[SwiftUI 100 天] Bookworm - part1 用 Binding 创建自定义组件

955 阅读2分钟
译自 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发挥作用的地方:它让我们创建一个在视图中可修改的值,这个值实际指向其他地方的某个值。对于Togglet,开关改变自身对于一个布尔型的本地绑定,而幕后实际上维护一个视图中的@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及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~