What you learned - 你学到了什么
希望您觉得这些项目开始让您有所收获,不仅可以进一步提高您的SwiftUI技能,还可以教给您一些更高级的Swift。另外,当然,您还创建了两个新的SwiftUI项目——您可以继续对其进行自定义,将它们放在GitHub上,或者将它们转换成更适合您的口味的东西。
以下是我们在最近三个项目中介绍的所有新内容的快速回顾:
- 为什么
@State
仅适用于结构体。 - 如何使
@ObservedObject
与类一起使用。 -
@Published
如何使我们对SwiftUI视图关注的属性的改变发出通知。 - 使用
sheet()
修饰符和presentationMode
呈现和消除视图。 - 使用
onDelete(perform:)
滑动删除。 - 添加
EditButton
到导航栏项,使用户可以更轻松地编辑列表数据。 - 使用
UserDefaults
读取和写入数据。 - 使用
Codable
归档和解归档数据,包括处理存储在多层次结构中的数据。 - 使用
Identifiable
协议来确保可以在我们的用户界面中唯一标识所有项目。 - 如何使用
GeometryReader
使内容适应屏幕。 - 使用
ScrollView
在可滚动区域中布置自定义视图。 - 使用
NavigationLink
将新视图推入导航堆栈。 - 使用Swift的泛型系统编写适用于各种数据的方法。
- 如何使用Swift的
first(where:)
方法在数组中找到与谓词匹配的第一个元素。 - 使用
layoutPriority()
调整为视图分配的空间。 - 创建自定义路径和形状。
- 使用
InsettableShape
创建可以嵌入并对其边界进行描边的形状。 - 使用
CGAffineTransform
创建旋转和平移。 - 使用
ImagePaint
制作创意边框和填充。 - 使用
drawingGroup()
以启用 Metal 来绘制复杂视图。 - 修改混合模式和饱和度。
- 使用
animatableData
和AnimatablePair
对形状进行动画处理。
我想您会同意这很多,而且范围也很广——我们已经从核心语言功能到面向用户的视图,甚至进一步发展到Swift绘图系统的创造性用途。有些人会喜欢纯语言的东西,而另一些人会喜欢编码的更具创造性的一面,没关系;我们都学到了不同的东西,没关系。
Key points - 关键点
尽管我们在前面的三个项目中介绍了很多内容,但是我想更详细地介绍三件事。不用担心——绘图不是其中之一!
类与结构体:有什么区别?为什么重要?
Swift为我们提供了两种创建自己的复杂数据类型的方法,重要的是要了解为什么要拥有它们以及为任何给定任务选择哪种类型。
类和结构体之间的根本区别在于,一个是值类型,另一个是引用类型。这些是我们如何处理数据的标准编程术语:数据只是一个简单的值(例如“ Hello”或5),还是仅仅是一个路标,上面写着“我的数据存储在此位置的RAM中”。
一旦您了解了不同,结构体和类就变成了两个截然不同的事物,但是当您学习这些差异时,根本感觉不到很大的不同。可以这样想:当我们创建一个包含结构体的变量时,数据实际上存储在该变量中,但是当我们使用一个类时,数据被放置在内存中的某个位置,并且该变量具有一个长数字来标识该数据的位置记忆。
这是“引用类型”名称的来源:存储为对某处某些内存的引用,有点像路标。它不是直接指向我的房子,而是指向我的房子的路标——而是一个间接层。这就是为什么如果将两个或多个变量指向一个类的同一实例,则它们可以修改相同的数据:您只有几个路标都指向同一个房屋。
这也是为什么引用类型和值类型用作常量时行为不同的原因。如果我们将一个类的实例固定不变,那么我们要做的就是制作一个恒定的路标——我们说过“此路标始终指向房屋编号24601,而不能指向其他房屋。”但是,这并不能阻止我们对房屋进行改建:也许我们想增加一层地板,或者更换厨房,或者甚至完全拆毁房屋并建造新的房屋。如果要固定这些东西(如果希望实际的房屋本身是恒定的),则需要为类使用恒定的属性。
因此,我们可以在具有可变数据(var numberOfFloors = 3
)的同时制作恒定的路标(let myHouse = House(
))。但是我们也可以翻转一下:我们可以制作一个具有恒定数据(var numberOfFloors = 3
)的可变路标(var myHouse = House()
),并且其行为也大不相同:我们可以移动路标,使其指向不同的房子,但是我们不能自己改造房子。
现在考虑一下这一切与Swift,SwiftUI甚至UIKit的关系。如果您在一个应用程序中有三个屏幕,并且它们共享相同的数据,那么确保数据在幕后(所有变量都包含相同的值)以及对用户(我们的所有列表/文本)保持同步是很重要的视图/等显示相同的值)。
SwiftUI提供了诸如@State
和@ObservedObject
之类的包装器,以确保我们的视图在数据更改时保持更新,但是这些视图不可与UIKit一起使用——您需要自己响应更改,然后更新用户界面以反映这些更改。
这产生了一个问题:
- 视图A可以创建一个类的实例。
- 视图A可以将其传递给视图B,以便他们共享它。
- 然后,视图B可以更改数据并更新其UI。
- 视图A将不知道数据已更改,并且将显示旧的UI。
因此,UIKit开发人员通常使用结构体来存储数据,因为这意味着每个视图都具有自己的数据副本,因此不会感到惊讶。更有趣的是,所有UIKit的视图类型都是使用类构建的,这意味着UIKit开发人员将其视图构建为类并将结构体用于其数据,这与SwiftUI完全相反。
对此感兴趣也可参考:Swift:内存管理和值类型的性能
明智地使用 UserDefaults
UserDefaults
可让我们轻松存储少量数据——它会自动附加到我们的应用程序中,这意味着它可以在我们的应用程序启动后立即加载。尽管它非常有用(而且您将在很大程度上依赖它!)它确实有两个缺点:
- 您只应在此处存储少量数据——超过512KB的内容可能会出问题。
- 您只能轻松存储某些类型的数据。其他所有内容都必须首先使用
Codable
来获取一些二进制数据。
这不是巧合:UserDefaults
实际上使用属性列表(如我们的Info.plist文件)写出其数据。实际上,牢记此条确实可以帮助您充分利用UserDefaults
——如果我们的Info.plist文件包含100,000个数据条目会很奇怪,而将100,000个项目放入UserDefaults
中也很奇怪。
因此,请按系统设计进行使用UserDefaults
——如Apple自己的文档所述,它被称为用户默认设置,因为它们通常用于确定应用程序在启动时的默认状态或默认情况下的行为方式。
何时使用泛型
我们使用泛型创建了一种解码方法,该方法能够从应用程序捆绑中获取任何JSON文件并将其加载到我们选择的Codable
类型中。但是,这可是个大事!首先,我们将该方法写为非通用方法:如果您还记得的话,它最初是对一组宇航员进行解码的,然后才升级为加载任何类型的Codable
类型。
那不是我在浪费时间,而是向您介绍了一种思考泛型和协议的明智方法。在这个项目中,我们需要从astronauts.json解码一组Astronaut
实例,因此我们编写了一种方法来精确地做到这一点——没有协议也没有泛型,只是Bundle
上的一个简单扩展即可帮助保持代码井井有条。这模仿了我们的大脑思维方式:我们可以理解诸如Astronaut
之类的具体事物,并且可以很容易地描述它们。
但是,使用协议和泛型并不是那么简单——我们现在可以使用多种可能的类型,除了遵循相同的协议之外,它们可能完全无关。例如,整数和字符串符合Swift内置的Comparable
协议,这就是为什么Swift知道如何对它们的数组进行排序的原因,否则它们是完全不同的东西。
可能令人困惑的是,我们无法比较两个可比较的对象,实际上,即使试图从一个方法返回Comparable
也不起作用。如果您不相信我,请尝试:
func makeString() -> Comparable {
"Hello"
}
那不会编译,并且有充分的理由:Comparable
本身并不意味着任何东西。就像我说的那样,字符串和整数都符合Comparable
协议,但这意味着您可以将一个整数与另一个进行比较,而不是可以将任何Comparable
类型与另一个进行比较——这毫无意义。
这就是为什么通用约束如此有用的原因:它们让我们说“只要……就可以是任何对象”,然后提供一些约束。而且——也许违反直觉——增加限制通常会启用更多功能。如您所见,当我们说我们的解码方法可以用于任何类型时,这意味着我们不能将JSONDecoder
与它一起使用。在我们明确添加Codable
限制之前,Swift才知道它可以安全地将JSON解码为该类型。
因此,良好使用泛型的关键是不要一开始就使用泛型,而当您确实需要泛型添加限制时,便可以获取最大的功能。
Challenge - 挑战
在继续进行下一批项目之前,您需要完成一个新的挑战。这意味着您将使用在前三个项目中获得的技能,从头开始构建一个完整的应用程序。
这次,您的目标是建立一个习惯跟踪应用程序,以供那些想要了解自己在某些方面做多少事情的人使用。那可能是学习一种语言,练习一种乐器,进行锻炼等等——他们可以决定他们添加了哪些活动,并随心所欲地跟踪它。
至少这意味着应该有一个他们要跟踪的所有活动的列表,以及一个添加新活动的表格——标题和描述就足够了。
对于更大的挑战,点按其中一项活动应显示一个带有说明的详细信息屏幕,完成该操作的次数以及一个增加其完成次数的按钮。
如果遇到更大的挑战,请使用Codable
和UserDefaults
加载和保存所有数据。
因此,此应用程序分为三个级别,您可以根据自己有多少时间和想要推动自己的距离来选择要走的距离。我确实建议您至少尝试一下每个级别——尽管您获得的每一点练习都有助于巩固您的学习!
提示:
- 从数据开始:定义一个包含单个活动的结构体,以及一个可以包含一系列活动的类。
- 该类将需要符合
ObservableObject
,并使用@Published
作为其属性。 - 您的主要清单和表单都应该能够读取共享活动对象。
- 确保您的活动符合
Identifiable
,以避免出现问题。 - 使用
sheet()
展示您的添加表单,并使用NavigationLink
展示您的活动详细信息视图(如果添加的话)。
这确实是一个有用的应用程序,特别是如果它是专门针对特定兴趣的应用程序——如果目标是练习一种乐器,那么您可以想象一个更高级的应用程序会建议要练习的其他事情,或者如果目标是锻炼,那么它可能会建议新的锻炼以使事情变得丰富。
实际上,这个挑战只是一个小应用程序,但我希望它至少能让您思考。祝好运!
译自 Hacking with iOS: SwiftUI Edition - 里程碑:项目 7 - 9
What you learned
Key points
Challenge
绘图项目——挑战 | Hacking with iOS: SwiftUI Edition | SwiftUI:为 @Published 属性添加 Codable 支持 |
---|
赏我一个赞吧~~~