Hacking with iOS: SwiftUI Edition 里程碑(一)之 Key points
- 2020 年 4 月 2 日
- 筆記

Key points – 要点
有三个要点值得更详细地讨论。这在一定程度上是在回顾我们所学到的东西——用不同的例子重新审视一遍,以确保它们是清楚的——但我也希望借此机会回答一些迄今可能出现的问题。
Structs vs Classes – 结构体 VS 类
首先,有一些你应该记忆犹新的东西:结构体和类。这两种方法都可以让我们使用属性和方法构建复杂的数据类型,但它们的工作方式(更具体地说,它们的不同之处)很重要。
如果您还记得,结构体和类之间有五个关键区别:
1.结构体默认有一个自动实现的成员初始化器类没有需要自己实现。 2.类可以使用继承来构建功能;结构不能。 3.如果复制一个类,两个副本都指向同一个数据;结构体的副本总是唯一的。 4.类可以有反初始化器;结构体没有。 5.可以更改常量类中的变量属性;无论属性是常量还是变量,常量结构体中的属性都是固定的。
在苹果最初的编程语言Objective-C中,我们几乎什么都用类——我们别无选择,因为它确实融入了我们的工作方式。
在Swift,我们确实有另一个选择,这个选择应该基于上述因素。我之所以说“应该”,是因为经常会看到一些人不关心这些差异,所以总是使用类或结构体,而不考虑这种选择的后果。
选择结构体还是类取决于您和您正在解决的特定问题。不过,我想让你考虑一下它是如何传达你的意图的。Donald Knuth说“程序应该是给人读的,只是顺带让计算机执行(programs are meant to be read by humans and only incidentally for computers to execute)”,这真的触及了我所说的核心:当有人读到你的代码时,他们对你的意图清楚吗?
如果您大多数时候使用结构体,那么在一个特定情况使用类就会传达一些意图:这个东西是不同的,需要不同的使用方式。如果你总是使用类,这种区别就会消失——毕竟,你不太可能经常需要它们。
提示:SwiftUI的一个迷人的细节是它如何完全颠倒我们使用结构体和类的方式。在UIKit中,我们会使用struct
来表示数据,使用class
来表示UI,但在SwiftUI中,情况完全相反,这很好地提醒了我们学习东西的重要性,即使你认为有些东西不是立马有用的。
使用ForEach
我想讨论的第二件事是ForEach
,我们已经将它用于如下代码:
ForEach(0 ..< 100) { number in Text("Row (number)") }
ForEach
是一个视图,就像SwiftUI中的大多数其他东西一样,但它允许我们在循环中创建其他视图。在这样做的同时,它还允许我们绕过SwiftUI规定的10个子视图的限制——ForEach
本身成为了10个子视图中的一个,而不是它里面的视图。
现在考虑这样一个字符串数组:
let agents = ["Cyril", "Lana", "Pam", "Sterling"]
我们如何循环这些并生成文本视图?
一种选择是使用我们已经使用过的结构:
VStack { ForEach(0 ..< agents.count) { Text(self.agents[$0]) } }
但是SwiftUI提供了另一种选择:我们可以直接在数组上循环。这需要更多的思考,因为SwiftUI想知道如何识别数组中的每个项。
考虑一下:如果我们在一个包含四项的数组上循环,我们将创建四个视图,但是如果body
被重新调用,并且我们的数组现在包含五项,那么SwiftUI需要知道哪个视图是新的,这样它就可以在UI中显示。SwiftIUI最不想做的就是扔掉它的整个布局,每次做一个小小的改变就从头开始。相反,它希望尽可能减少工作量:它希望保留现有的四个视图,只添加第五个视图。
所以,我们回到Swift如何识别数组中的值。当我们使用0..<5
或0..<agents.count
这样的范围时,Swift确信每个项目都是唯一的,因为它将使用范围中的数字——每个数字在循环中只使用一次,所以它肯定是唯一的。
在我们的字符串数组中,这是不可能的,但是我们可以清楚地看到每个值都是唯一的:在[“Cyril”,“Lana”,“Pam”,“Sterling”]
中的值不会重复。所以,我们可以告诉SwiftUI字符串本身——“Cyril”、“Lana”等可以用来唯一地标识循环中的每个视图。
我们可以在代码里面这样写:
VStack { ForEach(agents, id: .self) { Text($0) } }
因此,我们现在直接读取数组中的项,而不是在整数上循环并使用它读入数组,就像for
循环一样。
随着您使用SwiftUI的进展,我们将研究识别视图的第三种方法——使用Identifiable
协议。
使用绑定
当我们使用Picker
和TextField
等控件时,我们使用$propertyName
为它们创建到某种@State
属性的双向绑定。这对简单的属性非常有用,但是有时候——只是有时候,希望如此!——您可能需要更高级的东西:如果您想运行一些逻辑来计算当前值,该怎么办?或者,如果你不只是想在写的时候隐藏一个值呢?
如果我们想对绑定中的更改做出反应,我们可能会尝试利用Swift的didSet
属性观察,但您会失望的。这就是自定义绑定的来源:它们可以像@State
绑定一样使用,但我们无法完全控制它们的工作方式。
SwiftUI为我们做的每件事都可以手工完成,尽管依赖自动解决方案几乎总是更好的,但在窥视一下幕后确实会有帮助,这样你就能了解它在为你做什么。
首先,让我们看看最简单的自定义绑定形式,它将值存储在另一个@State
属性中,并将其返回:
struct ContentView: View { @State var selection = 0 var body: some View { let binding = Binding( get: { self.selection }, set: { self.selection = $0 } ) return VStack { Picker("Select a number", selection: binding) { ForEach(0 ..< 3) { Text("Item ($0)") } }.pickerStyle(SegmentedPickerStyle()) } } }
因此,该绑定实际上只是作为一个传递——它本身不存储或计算任何数据,而是作为UI和被操纵的底层状态值之间的一个填充。
但是,请注意,选择器现在是使用selection:binding
进行的,不需要美元符号。我们不需要在这里明确要求双向绑定,因为它已经是了。
如果我们愿意的话,我们可以创建一个更高级的绑定,它不仅仅传递一个值。例如,假设我们有一个带有三个切换开关的表单:用户是否同意条款和条件,同意隐私策略,并同意接收有关发货的电子邮件。
我们可以将其表示为三个布尔型的@State
属性:
@State var agreedToTerms = false @State var agreedToPrivacyPolicy = false @State var agreedToEmails = false
尽管用户可以手动切换它们,但我们可以使用自定义绑定一次完成所有操作。如果这三个布尔值都为真,则此绑定将为真,但如果更改了绑定,则它将更新所有布尔值,如下所示:
let agreedToAll = Binding( get: { self.agreedToTerms && self.agreedToPrivacyPolicy && self.agreedToEmails }, set: { self.agreedToTerms = $0 self.agreedToPrivacyPolicy = $0 self.agreedToEmails = $0 } )
所以现在我们可以创建四个切换开关:这三个选择都使用布尔值表示同意或不同意,一个同时同意或不同意所有三个的控制开关:
struct ContentView: View { @State var agreedToTerms = false @State var agreedToPrivacyPolicy = false @State var agreedToEmails = false var body: some View { let agreedToAll = Binding<Bool>( get: { self.agreedToTerms && self.agreedToPrivacyPolicy && self.agreedToEmails }, set: { self.agreedToTerms = $0 self.agreedToPrivacyPolicy = $0 self.agreedToEmails = $0 } ) return VStack { Toggle(isOn: $agreedToTerms) { Text("Agree to terms") } Toggle(isOn: $agreedToPrivacyPolicy) { Text("Agree to privacy policy") } Toggle(isOn: $agreedToEmails) { Text("Agree to receive shipping emails") } Toggle(isOn: agreedToAll) { Text("Agree to all") } } } }
再说一遍,定制绑定并不是你经常想要的,但是花点时间看看幕后,了解发生了什么是非常重要的。尽管它非常聪明,但它只是一个工具,而不是魔法!