SwiftUI内功之深入研究Binding

绑定是SwiftUI展示给我们的几个属性包装器之一,用于控制应用程序中的数据流。绑定为我们提供了类似于访问值类型的参考。本文,我们将了解如何以及何时使用绑定。我们将学习如何在SwiftUI中使用绑定时避免常见错误。

基础

绑定是一种属性包装器类型,可以读取和写入数值。在SwiftUI中,我们有几种可能数据来源。它可以是EnvironmentObject,ObservedObject或State。所有这些属性包装器都提供了一个绑定的投影值。让我们看一个简单的例子。

import SwiftUI

struct ExampleView: View {
    @State private var text = "Hello World."

    var body: some View {
        TextField("type something...", text: $text)
    }
}

在这里,我们有一个State变量。我们还有一个TextField,它需要一个文本值的绑定。我们使用$符号访问状态属性包装器的预计值,该值是对属性包装器值的绑定。

常见错误

让我们看一个更复杂的例子。我们将构建一个显示用户列表并允许我们编辑用户数据的应用程序。

import SwiftUI
import Combine

struct Person: Identifiable {
    let id: UUID
    var name: String
    var age: Int
}

final class PersonStore: ObservableObject {
    @Published var persons: [Person] = [
        .init(id: .init(), name: "Majid", age: 28),
        .init(id: .init(), name: "John", age: 31),
        .init(id: .init(), name: "Fred", age: 25)
    ]
}

struct PersonsView : View {
    @ObservedObject var store: PersonStore

    var body: some View {
        NavigationView {
            List {
                ForEach(store.persons.indexed(), id: \.1.id) { index, person in
                    NavigationLink(destination: EditingView(person: self.$store.persons[index])) {
                        VStack(alignment: .leading) {
                            Text(person.name)
                                .font(.headline)
                            Text("Age: \(person.age)")
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                    }
                }
            }.navigationBarTitle(Text("Persons"))
        }
    }
}

请记住,绑定的值必须是值类型。这意味着它必须是枚举或结构。我看到人们有时使用类来描述EnvironmentObject或ObservedObject内的状态或条目,并注意到绑定无效。 Apple关于绑定的文档说:如果Value不是值语义,则未指定使用所得Binding的任何视图的更新行为.

struct EditingView: View {
    @Environment(\.presentationMode) var presentation
    @Binding var person: Person

    var body: some View {
        Form {
            Section(header: Text("Personal information")) {
                TextField("type something...", text: $person.name)
                Stepper(value: $person.age) {
                    Text("Age: \(person.age)")
                }
            }

            Section {
                Button("Save") {
                    self.presentation.wrappedValue.dismiss()
                }
            }
        }.navigationBarTitle(Text(person.name))
    }
}

如您在上面的示例中看到的,我们使用绑定将可写引用传递给人员结构。一旦我们在EditingView中修改了用户实例,SwiftUI就会更新PersonsView以遵守更改。

extension Sequence {
    func indexed() -> Array<(offset: Int, element: Element)> {
        return Array(enumerated())
    }
}

您可以看到我使用索引函数生成了一个元组数组,该数组提供了元素及其索引。它使我可以读取项目以将其渲染到位,并使用项目的索引访问绑定。

计算绑定

通常,我们使用真实来源的预计值访问绑定。在本节中,我们将讨论创建绑定的另一种方法。绑定是数据和访问数据的视图之间的双向连接。 SwiftUI提供了一种使用getter和setter闭包构造绑定的方法。在这种情况下,我们负责计算这些闭包内部的值。很难想象我们可以在哪里使用它,但是它在类似Redux的状态容器中可以很好地发挥作用。

typealias Reducer = (inout State, Action) -> Void

final class Store: ObservableObject {
    @Published private(set) var state: State
    private let reducer: Reducer

    init(initialState: State, reducer: Reducer) {
        self.state = initialState
        self.reducer = reducer
    }

    func send(_ action: Action) {
        reducer(&state, action)
    }
}

在这里,我们有一个存储概念,可以保存应用程序的整个状态。状态的所有变化都来自单向流。 Reducer是我们可以改变应用程序状态的唯一位置。通过使用计算绑定,我们可以提供对状态的只读访问,并通过将操作发送给reducer来重塑单向流。

extension Store {
    func binding(
        for keyPath: KeyPath,
        transform: @escaping (Value) -> Action
    ) -> Binding {
        Binding(
            get: { self.state[keyPath: keyPath] },
            set: { self.send(transform($0)) }
        )
    }
}

如您所见,我们生成了一个计算绑定,该绑定读取状态的一部分,并在需要时通过reducer发出操作以修改状态。例如,当您有一个描述某些绑定到应用程序状态的复选框的设置屏幕时,可能需要这种类型的绑定

Constant binding

创建绑定的另一种方法是静态常量函数。此函数使我们可以创建一个提供值的绑定,但忽略其上的任何突变。换句话说,它为提供的值生成一个不变的绑定。

import SwiftUI

struct ExampleView: View {
    var body: some View {
        TextField("type something...", text: Binding.constant("Hello!"))
    }
}

结论

今天,我们学习了另一个出色的工具来控制SwiftUI中的数据流。绑定可以是比其他工具更复杂的工具,但是我相信我们涵盖了在SwiftUI中有效使用绑定的所有必要条件。

技术交流

QQ:3365059189
SwiftUI技术交流QQ群:518696470

  • 请关注我的专栏icloudend, SwiftUI教程与源码
    https://www.jianshu.com/c/7b3e3b671970

你可能感兴趣的:(SwiftUI内功之深入研究Binding)