欢迎光临散文网 会员登陆 & 注册

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

2023-03-22 12:00 作者:爱上树の蜗牛  | 我要投稿

原创链接: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是什么类型的数据?我可以想到几种可能性:

  1. 也许它是另一个User对象,现在经过修改,以便在其已知语言数组中包含 Swift。

  2. 也许它是一个Programmer结合了用户和语言的对象。

  3. 也许这是对经典恐怖电影 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存储在数据模型中。

请记住,你已经知道完成这项工作所需的一切——祝你好运!



SwiftUI学习100天(Day77 - 里程碑:项目 13 - 15)的评论 (共 条)

分享到微博请遵守国家法律