译自 www.hackingwithswift.com/books/ios-s…
更多内容,欢迎关注公众号 「Swift花园」
喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀
Bookworm:介绍
在这个项目中我们要构建一个应用,用于跟踪你读过的书和你对它们的看法,延续 Cupcake Corner 项目的做法:让我们用上你已经掌握的技能,并且增加一些额外奖励的新技能,让你的技能熟练度再上一个台阶。
这一回你将认识 Core Data,它是 Apple 的一个久经战阵的框架,用于处理数据框。这个项目将扮演 Core Data 介绍者的角色,稍后我们会介绍更多细节。
与此同时,我们还将构建我们的第一个自定义组件 —— 一个评价等级的星星 widget,用户可以点击它来为每本书评分。这也意味着,我们将引入一个新的属性包装器,它叫@Binding
。
如往常一样,我们先速览一遍你将在这个项目中用到的新技术。首先,请创建一个新的 iOS 应用,取名叫 Bookworm,使用 Single View App 项目模板。
译自 www.hackingwithswift.com/books/ios-s…
用 Binding 创建自定义组件
你已经了解 SwiftUI 的@State
属性包装器如何同本地值类型协同工作,以及@ObservedObject
如何同共享的引用类型协同工作。其实还有第三个选项,叫@Binding
, 它能让我们将一个视图的@State
属性连接到底层的模型数据。
想想看:当我们创建一个 toggle 开关时,我们像下面这样发送某个可以被改变的布尔属性:
@State private var rememberMe = false
var body: some View {
Toggle(isOn: $rememberMe) {
Text("Remember Me")
}
}
因此,Toggle
需要在用户和它交互时改变我们的布尔值,但它是如何记住自己应该改变成哪个值呢?
这正是@Binding
发挥作用的地方:它让我们创建一个在视图中可修改的值,这个值实际指向其他地方的某个值。对于Toggle
t,开关改变自身对于一个布尔型的本地绑定,而幕后实际上维护一个视图中的@State
属性。
这使得@Binding
对于任何自定义 UI 组件都至关重要,其中的关键在于 UI 组件就像其他东西一样,只是 SwiftUI 视图,但@Binding
使得它们被区分开:它们既可以有本地的@State
属性,同时也暴露@Binding
属性,以便自己可以和其他视图连接。
为了说明这一点,我们将创建一种新的按钮:这种按钮在按下时保持“被按下”的视觉效果。基本的实现方式和你之前见到过的一样:一个带填充的按钮,线性渐变的背景,Capsule
的裁切形状,等等 —— 把下面的代码添加到 ContentView.swift :
struct PushButton: View {
let title: String
@State var isOn: Bool
var onColors = [Color.red, Color.yellow]
var offColors = [Color(white: 0.6), Color(white: 0.4)]
var body: some View {
Button(title) {
self.isOn.toggle()
}
.padding()
.background(LinearGradient(gradient: Gradient(colors: isOn ? onColors : offColors), startPoint: .top, endPoint: .bottom))
.foregroundColor(.white)
.clipShape(Capsule())
.shadow(radius: isOn ? 0 : 5)
}
}
这里我用了属性来控制渐变色,以便我们可以定制按钮的背景。
我们现在可以在主界面中使用刚才创建的按钮,像这样:
struct ContentView: View {
@State private var rememberMe = false
var body: some View {
VStack {
PushButton(title: "Remember Me", isOn: rememberMe)
Text(rememberMe ? "On" : "Off")
}
}
}
按钮下方有一个文本视图,以便我们追踪按钮状态 —— 尝试运行代码,观察按钮的工作方式。
你将发现,点击按钮确实会影响它的呈现效果,但我们的文本视图并不会反映这个变化 —— 它一直都是 “Off”。显然,在按钮点击时有改变发生了,因为按钮的外观发生了变化,但这个变化并没有在ContentView
中体现。
这里的情况是我们实际上定义了一种单向的数据流:ContentView
拥有rememberMe
布尔属性,并且被用于创建PushButton
—— 这个按钮有一个由ContentView
. 提供的初始值。但是,一旦按钮创建,它就接管了那个值:它在按钮内部触发isOn
属性在true
或者false
间变化,但并不会把变化传回ContentView
。
这是一个问题,因为这样一来我们就有两个 sources of truth:ContentView
存储一个值,而PushButton
存储另一个。幸运的是,借助@Binding
:我们可以在PushButton
和任何使用它的东西之间建立双向的连接,以便一边改变,另一边也同步。
为了切换到@Binding
,我们只需要做两处改动。首先,在PushButton
里,把它的isOn
属性改成这样:
@Binding var isOn: Bool
其次,在ContentView
里,修改我们创建按钮的方式,变成下面这样:
PushButton(title: "Remember Me", isOn: $rememberMe)
我们在rememberMe
之前加了一个$
符号 —— 代表我们传入的是绑定,而不是布尔类型。
我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~