SwiftUI学习100天(Day77 - 里程碑:项目 13 - 15)

原创链接:https://www.hackingwithswift.com/100/swiftui
以下内容仅供学习参考:

在我们处理 UIKit 和 SwiftUI 集成的各个部分时,你已经完成了几个困难的项目,所以让我们暂停一下,回顾一下学到的东西。

今天是时候停下来反思一下所涵盖的内容了。我们还将更详细地深入探讨两个主题——运算符重载和属性包装器,因为这样做你将对我们一直在使用的内容有更深入的了解。还有今天的挑战,你需要运用所学的技能来构建全新的应用程序。
挑战为你提供了以实际方式测试你的学习的机会,为你提供了一个沙盒,你可以在其中真正表达自己并根据需要扩展自己,我希望也能向你展示当你下定决心时的能力它。正如 Cicely Tyson 所说,“挑战会让你发现你从未真正了解过的自己,”我希望你今天能够发现你已经走了多远!
今天你有三个主题要完成,其中之一是你的挑战。

你学到了什么
这些项目开始向你介绍 SwiftUI 中更困难的部分,尽管这些都不是真正的 SwiftUI 的错——正是 SwiftUI 与 Apple 的旧框架相遇的地方让事情变得有点艰难。随着时间的推移,这些粗糙的边缘会变得平滑一些,但可能需要几年时间,而且无论如何,对于任何时候你想要集成来自 Apple 外部的代码,这些仍然很重要。
在完成这三个项目的同时,你还了解到:
属性包装器如何成为结构体
显示带有大量按钮的确认对话框
使用 Core Image 操作图像
集成
PHPickerViewController
到 SwiftUI编写可以充当图像选择器委托的协调器类
将地图集成到 SwiftUI 中
在地图上放置图钉
将图像保存到用户的照片库
添加对自定义类型
Comparable
的一致性查找并写入用户的文档目录
写入时启用文件加密
使用 Touch ID 和 Face ID 对用户进行身份验证
这些主题中的每一个都作为一个独立的技术进行了介绍,然后应用到一个真实的项目中,所以希望它能真正融入其中!



关键点
今天我想深入探讨两个主题,这两个主题都非常高级,但我知道它们确实有助于加深你对前三个项目所涵盖内容的理解。
运算符重载
当我们添加自定义一致性Comparable
时,
我们需要添加一个名为<
. 这反过来又允许 Swift 比较诸如 a < b
之类的表达式
,这使我们能够访问sorted()
不带参数的版本。
这称为运算符重载,它允许我们使用相同的+
运算符添加两个整数或连接两个字符串。如果需要,你可以定义自己的运算符,但扩展现有运算符来执行新操作也很容易。
例如,我们可以添加一些扩展Int
,让我们将一个Int
和一个Double
相乘
——这是 Swift 默认不允许的,这可能很烦人:
请特别注意参数:它使用 一个Int
作为左手操作数和 一个Double
作为右手操作数,这意味着如果你交换这两个参数,它将无法工作。所以,如果你想要完整——如果你想让任何一个命令都起作用——你需要定义方法两次。
然而,如果你想要真正的完整,那么扩展Int
是错误的选择:我们应该使用一个协议来包装Int
以及其他整数类型,比如我们在 Core Data 中使用的Int16
。Swift 将所有整数类型放入一个名为 BinaryInteger
的协议
中,如果我们在其上编写扩展,则Self
(使用大写 S)指代正在使用的任何特定类型。所以,如果它用在Int
上,则Self
意味着Int
,如果它用在Int16
上,则意味着它被替代。
下面是一个拓展,它在任何类型的整数和
,无论整数是在左边还是右边:Double
之间添加*
如果你想知道,Swift 不为我们启用这些运算符是有原因的:它不能保证像你希望的那样准确。作为一个简单的例子,试试这个:
这将创建一个 64 位整数,其中包含 50 千万亿零一。然后使用我们的自定义扩展将它乘以Double
1.0,这在理论上意味着返回的数字应该与Int
. 但是,该String(format:_:)
调用要求打印没有小数位的数字,你会发现它与整数不同:它是 50 千万亿,没有 1。现在,你可能会问“什么是 1,当你正在使用 50 quadrillion”,这很好——我在这里不是要告诉你什么是对的,什么是错的,只是说如果你想要绝对的准确性,你应该避免使用这些辅助方法。
更一般地说,我想给你一个关于运算符重载的警告。当我在项目 14 中介绍它时,我说过运算符重载“既是福也是祸”,我想简要地谈谈为什么会这样。
考虑这样的代码:
result
是什么类型的数据
?我可以想到几种可能性:
也许它是另一个
User
对象,现在经过修改,以便在其已知语言数组中包含 Swift。也许它是一个
Programmer
结合了用户和语言的对象。也许这是对经典恐怖电影 The Fly 的奇怪翻拍。
关键是我们不知道,如果不阅读相关+
运算符的源代码,我们就无法真正分辨出来。
现在考虑这段代码:
我认为这更清楚了:我们现在正在一个对象上运行一个简单的方法,你可能会猜到我们正在变异paul
以将 Swift 包含在编程语言的数组中。
任何优秀的开发人员都会告诉你,清晰度是编写良好的代码最重要的特征之一——我们需要在编写的内容中明确表达我们的意思,因为它将来会被阅读数十或数百次。
因此,一定要将运算符重载添加到你可以用来解决问题的技能库中,事实上,我在我的《Pro Swift 》一书中对它们进行了更详细的介绍,但始终要小心使用它们。
自定义属性包装器
你已经看到属性包装器实际上只是一些技巧:它们采用一个简单的值并将其包装在另一个值中,以便可以添加一些额外的功能。这可能是 SwiftUI 用@State
来
在别处存储值的方式,或者它用@Environment
来
从共享数据源读取值的方式,但原理是相同的:它采用一个简单的值并以某种方式赋予它超能力。
我们可以在自己的代码中使用属性包装器,你可能想要这样做的原因有很多。与运算符重载一样,如果你尝试过它们,你将更了解它们是如何工作的,但也值得深思熟虑地使用它们:如果它们是你首先接触到的东西,你可能会犯错误。
为了演示属性包装器,我想从一个包装某种BinaryInteger
值的简单结构开始。当涉及到设置它的包装值时,我们将给它一些自定义代码,这样如果新值低于 0,我们就会将它精确地设置为 0,这样这个结构就永远不会为负。
我们的代码看起来像这样:
我们现在可以用一个整数来创建它,但如果该整数低于 0,那么它将被固定为 0。因此,这将打印 0:
属性包装器让我们做的是将其用于结构或类中的任何类型的属性。更好的是,它只需要一个步骤:在结构体NonNegative
之前编写
,如下所示:@propertyWrapper
就是这样——我们现在有了自己的属性包装器!
如果你没有从他们的名字中猜到,属性包装器只能用于属性而不是普通变量或常量,所以为了尝试我们的,我们将把它放在这样的User
结构中:
现在我们可以创建一个用户并自由添加或删除分数,确定分数永远不会低于 0:
如你所见,这里真的没有魔法:属性包装器不仅只是语法糖,可以使一个数据包裹另一个数据,而且如果需要,我们也可以自己制作它们。



挑战
你是否曾经参加过会议或聚会,与新认识的人聊天,然后在你走开几秒钟后意识到呢已经忘记了他们的名字?你并不孤单,你今天构建的应用程序将有助于解决该问题和其他类似问题。
你的目标是构建一个应用程序,要求用户从他们的照片库中导入图片,然后为他们导入的任何内容附加一个名称。他们命名的完整图片集应显示在 List
中
,点击列表中的项目应显示带有较大版本图片的详细信息屏幕。
分解它,你应该:
包裹
PHPickerViewController
,以便它可以用来选择照片。检测何时导入新照片,并立即要求用户为照片命名。
将该名称和照片保存在安全的地方。
在列表中显示所有姓名和照片,按姓名排序。
创建一个显示全尺寸图片的详细信息屏幕。
确定保存所有这些数据的方法。
我们已经介绍了如何使用 UIImageWriteToSavedPhotosAlbum()
将数据保存到用户的照片库
,但是将图像保存到磁盘需要一个小的额外步骤:你需要UIImage
通过Data
调用它的jpegData()
方法将你的转换为:
该compressionQuality
参数可以是 0(极低质量)和 1(最高质量)之间的任何值;0.8 之类的值可以在大小和质量之间进行很好的权衡。
如果你愿意,你可以为这个项目使用核心数据,但这不是必需的——一个简单的 JSON 文件写到文档目录就可以了,尽管你需要添加一个自定义的Comparable
一致性来
让数组排序工作。
如果你确实选择使用 Core Data,请确保你没有将实际图像保存到数据库中,因为那样效率不高。不管是否有 Core Data,最佳做法是为图像文件名生成一个新UUID
文件名,然后将其写入文档目录,并将其UUID
存储
在数据模型中。
请记住,你已经知道完成这项工作所需的一切——祝你好运!


