专栏首页韦弦的偶尔分享Hacking with iOS: SwiftUI Edition - 里程碑:项目 16 - 18

Hacking with iOS: SwiftUI Edition - 里程碑:项目 16 - 18

What you learned - 你学到了什么

最近我们进行了一些非常漫长的项目,但这主要是由于您的SwiftUI技能真正得到了增长——您现在已经超出了基础知识,因此您能够解决更大的项目来解决更大的问题。我意识到在这些较大的项目上工作会感到很累,但我希望您能够回顾自己的成果并感觉良好——您走了很长一段路!

在完成这些项目时,您还了解了:

  • 使用@EnvironmentObject读取环境值。
  • 使用TabView创建 Tabs。
  • 使用 Swift 的 Result 类型返回成功或失败信息。
  • 使用objectWillChange.send()手动发布ObservableObject的变更。
  • 控制图像插值。
  • 将按钮放在ContextMenu中。
  • 使用UserNotifications框架创建本地通知。
  • 通过Swift package dependencies 使用第三方代码。
  • 使用map()filter()基于现有数组创建新数组。
  • 如何创建动二维码。
  • 将自定义手势附加到SwiftUI视图。
  • 使用UINotificationFeedbackGenerator使iPhone振动。
  • 使用·allowHitTesting()`控制用户交互。
  • 使用计时器重复触发事件,或通过从NotificationCenter接收事件来触发事件。
  • 支持色盲,减少动画等辅助性功能。
  • 横向使用带有StackNavigationViewStyleNavigationView
  • SwiftUI的三步布局系统。
  • Alignment,alignment guides和自定义alignment guides。
  • 使用position()修饰符绝对定位视图。
  • 使用GeometryReaderGeometryProxy实现特殊效果。

…并且您还构建了一些真正的应用程序来将这些技能付诸实践——真的很忙,希望您为自己的成就感到自豪!

Key points - 关键点

在我们继续进行该项目的挑战之前,我想深入探讨两点,以确保您已充分理解它们:map()filter()如何适应更大的函数式编程世界,以及Swift的Result类型。

Functional programming - 函数式编程

尽管我在《Pro Swift》一书中对函数式编程进行了很多介绍,但我也想在这里进行介绍,因为我们在项目16中使用了两次,一次是与map()一起使用,一次是与filter()一起使用。这两种方法都是为了让我们指定想要的东西,而不是如何达到目的而设计的,这两种方法都是广泛编程方法的一部分它被称为函数式编程。

为了演示此方法与称为命令式编程的通用替代方法有何不同,请看以下代码:

let numbers = [1, 2, 3, 4, 5]
var evens = [Int]()

for number in numbers {
    if number.isMultiple(of: 2) {
        evens.append(number)
    }
}

这将创建一个整数数组,一个一个地循环遍历,然后将2的倍数添加到名为偶数的新数组中——我们需要确切说明我们希望过程如何发生。该代码易于阅读,易于编写并且运行良好,但是如果我们要使用filter()重写它,则会得到以下信息:

let numbers = [1, 2, 3, 4, 5]
let evens = numbers.filter { $0.isMultiple(of: 2) }

现在,我们不需要弄清楚事情应该如何发生,而只需关注我们想要发生的事情:我们为filter()提供可以执行的测试,其余的都可以自动完成。这意味着我们的代码更短,很棒,但是它还通过其他三种方式得到了改进:

  1. 不再可能在循环内插入意外break —— filter()将始终处理数组中的每个元素,这种额外的简单性意味着我们可以专注于测试本身。
  2. 除了提供闭包之外,我们还可以调用共享函数,这对于代码重用非常有用。
  3. 现在,所得的evens数组是恒定的,因此以后我们不能无意中对其进行修改。

编写更少的代码总是很不错,但是编写更简单,更可重用且变量更少的代码则更好!

接受函数作为参数或将函数作为返回值的函数称为高阶函数,而map()filter()都是它的示例。 Swift 还有更多类似的东西,但是最有用的之一是compactMap()

  1. 就像map()一样,对数组中的每个项目运行转换函数。
  2. 将该转换函数返回的所有可选参数解包,并将结果放入要返回的新数组中。
  3. 任何为nil的可选项都将被丢弃。

因此,虽然map()将创建一个新数组,其中包含与其所使用的数组相同数量的项目,但compactMap()可能返回相同数量,更少的项目,甚至根本没有!

要查看实际的map()compactMap()之间的区别,请尝试以下示例:

let numbers = ["1", "2", "fish", "3"]
let evensMap = numbers.map(Int.init)
let evensCompactMap = numbers.compactMap(Int.init)

这将创建一个字符串数组,然后使用map()compactMap()将其转换为整数数组。运行该代码时,evensMap将包含两个可选整数,然后是nil,然后是另一个可选整数,而evensCompactMap将包含三个实整数——没有可选项,也没有nil。好多了!

Result

我们使用Swift的Result类型作为返回成功或失败的单个值的简单方法,但是我认为有一些重要功能对您自己的代码有用。

首先,如果您考虑一下,结果就像是一个稍微高级的可选形式。可选参数要么包含某种值(整数,字符串等),要么根本不包含任何值,而Result也包含某种值,但是对于替代情况而言,Result现在不包含任何值,它必须包含某种错误。

在幕后,可选值和Result都实现为带有两种情况的Swift枚举。对于可选值,该枚举称为Optionalnil对应.none,.some对应您的整数/字符串/等关联值;对于Result,它们是.success,具有关联的值,.failure是另一个关联的值。

两者之间的唯一真正区别是,Swift的可选选项使用了语法糖——特殊语法旨在使我们的生活更轻松,因为可选项非常常见。因此,对于可选对象来说,存在if let和可选链之类的东西,而Result则没有任何特殊代码。

其次,您已经看到Result包含某种成功值或某种错误值,但是如果您需要它,Result可以抛出异常函数方法。

如果您有一个Result并想使用do/catch,只需调用Resultget()方法——如果成功 -> 值存在 -> 它将返回成功值,否则将抛出错误。

例如,如下代码:

enum NetworkError: Error {
    case badURL
}

func createResult() -> Result<String, NetworkError> {
    return .failure(.badURL)
}

let result = createResult()

它定义了某种错误,创建了一个返回字符串或错误的函数(但实际上总是返回一个错误),然后调用该函数并将其返回值放入结果中。如果要使用具有该值的do / catch,可以按如下的方式使用get()

do {
    let successString = try result.get()
    print(successString)
} catch {
    print("Oops! There was an error.")
}

反之——从抛出代码创建Result值——您会发现Result具有一个接受抛出闭包的初始化程序。如果闭包返回一个正常可用的值,则会赋值为成功的case,否则将引发的错误放入失败的case。

例如:

let result = Result { try String(contentsOf: someURL) }

在该代码中,结果将为Result<String, Error>——它没有特定类型的Error,因为String(contentsOf :)没有返回。

关于Result,您应该了解的最后一件事是它具有您已经习惯的功能方法,包括map()mapError()。例如,map()方法在Result内部查找,并使用您指定的闭包将成功值转换为另一种值——例如,它可能会将字符串转换为整数。但是,如果发现失败,它将直接使用它,而忽略您的转换。另外,mapError()可以将错误从一种类型转换为另一种类型,如果您想在一个位置上统一错误类型,这可能会很有用。

这是关于函数式编程的众多爱好之一:一旦了解了map()的“采用闭包并将其用于转换东西”的性质,您就会发现它存在于数组,Result甚至Optional中!

Challenge - 挑战

这次挑战可能很容易,也可能很难,具体取决于您想挑战多远,但该项目的核心很简单:您需要构建一个可帮助用户掷骰子然后存储其结果的应用程序。

至少应该有一个选项卡视图,其中第一个选项卡允许用户掷骰子,第二个选项卡显示先前掷骰的结果。但是,如果您想进一步提高自己的实力,可以尝试以下一种或多种方法:

  1. 让用户自定义滚动的骰子:骰子的数量和类型:4面,6面,8面,10面,12面,20面,甚至100面。
  2. 显示掷骰子的总数。
  3. 使用 Core Data 存储结果,使结果持久化。
  4. 掷骰子时添加触控反馈。
  5. 对于真正的挑战,请在决定最终数字之前,使骰子滚动的值经过各种可能的值。

当我说“掷骰子”时,您无需创建精美的3D效果-只需显示“掷骰子”的数字即可。

唯一可能需要您做些事情的是步骤5:在确定最终数字之前,使结果在各种值之间滑动。解决此问题的最简单方法是通过在一定数量的调用后取消Timer的计时器,但是如果您想使用更高级的解决方案,则可以尝试使用增加的延迟来调用DispatchQueue.main.asyncAfter(),这样它的启动速度快于其减速速度, “骰子滚”放慢。

在工作时,请花点时间记住代码的可访问性——尝试将其与VoiceOver一起使用,并确保它能正常工作。

译自 : What you learned Key points Challenge

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Hacking with iOS: SwiftUI Edition - 里程碑:项目 7 - 9

    希望您觉得这些项目开始让您有所收获,不仅可以进一步提高您的SwiftUI技能,还可以教给您一些更高级的Swift。另外,当然,您还创建了两个新的SwiftUI项...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition - 里程碑:项目 4 - 6

    在这一点上,您应该真的开始对SwiftUI的工作方式感到满意。我知道,对于某些人来说,这可能是一个巨大的心理障碍,因为我们失去了控制程序的精确流程的能力,而是需...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition - 里程碑:项目 13 - 15

    这些项目开始向您介绍SwiftUI的更困难的部分,尽管这些都不是真正的SwiftUI的错——在SwiftUI与Apple的旧框架相遇的地方,事情变得有些粗糙。随...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition - 里程碑:项目 10 - 12

    这最后三个项目确实推动了数据的开发,首先是通过互联网发送和接收数据,然后进入Core Data,以便您可以了解实际应用如何管理其数据。您在此项目中学到的技能也许...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition 里程碑(一)之 Key points

    有三个要点值得更详细地讨论。这在一定程度上是在回顾我们所学到的东西——用不同的例子重新审视一遍,以确保它们是清楚的——但我也希望借此机会回答一些迄今可能出现的问...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition - iExpense 项目

    我们接下来的两个项目将开始把你的SwiftUI技能推向基础之外,因为我们将探索具有多个屏幕、加载和保存用户数据以及具有更复杂用户界面的应用程序。

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition 里程碑(一)之 What you learned?

    你现在已经完成了前两个SwiftUI项目,并且也完成了一个技术项目——两个应用程序和一个技术项目的节奏将持续到课程结束,这将帮助你快速提升知识,同时花时间回顾和...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition - Flashzilla 项目(二)

    用户可以向左或向右滑动我们的卡片,以将其标记为正确猜对与否,但这两个方向之间没有视觉上的区别。从 探探 等约会应用中借用控件,我们将向右滑动(他们正确猜出了答案...

    韦弦zhy
  • Hacking with iOS: SwiftUI Edition - SnowSeeker 项目(一)

    在此应用中,我们将同时显示两个视图,就像 Apple 的 Mail 和 Notes 应用一样。在 SwiftUI 中,这是通过将两个视图放入Navigation...

    韦弦zhy

扫码关注云+社区

领取腾讯云代金券