Swift Combine 使用 ObservableObject 与 SwiftUI 模型作为发布源 从入门到精通二十

Combine 系列

  1. Swift Combine 从入门到精通一
  2. Swift Combine 发布者订阅者操作者 从入门到精通二
  3. Swift Combine 管道 从入门到精通三
  4. Swift Combine 发布者publisher的生命周期 从入门到精通四
  5. Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
  6. Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
  7. Swift 使用 Combine 进行开发 从入门到精通七
  8. Swift 使用 Combine 管道和线程进行开发 从入门到精通八
  9. Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
  10. Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
  11. Swift Combine 用 Future 来封装异步请求 从入门到精通十一
  12. Swift Combine 有序的异步操作 从入门到精通十二
  13. Swift Combine 使用 flatMap 和 catch错误处理 从入门到精通十三
  14. Swift Combine 网络受限时从备用 URL 请求数据 从入门到精通十四
  15. Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五
  16. Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六
  17. Swift Combine 合并多个管道以更新 UI 元素 从入门到精通十七
  18. Swift Combine 通过包装基于 delegate 的 API 创建重复发布者 从入门到精通十八
  19. Swift Combine 响应NotificationCenter 的更新 从入门到精通十九
    在这里插入图片描述

1. 和 SwiftUI 集成 使用 ObservableObject 与 SwiftUI 模型作为发布源

目的: SwiftUI 包含 @ObservedObjectObservableObject 协议,它为 SwiftUI 的视图提供了将状态外部化的手段,同时通知 SwiftUI 模型的变化。

SwiftUI 视图是基于某些已知状态呈现的声明性结构,当该状态发生变化时,这些当前的结构将失效并更新。 我们可以使用 Combine 来提供响应式更新来操纵此状态,并将其暴露回 SwiftUI。 此处提供的示例是一个简单的输入表单,目的是根据对两个字段的输入提供响应式和动态的反馈。

以下规则被编码到 Combine 的管道中:

  1. 两个字段必须相同 - 如输入密码或电子邮件地址,然后通过第二个条目进行确认。
  2. 输入的值至少为 5 个字符的长度。
  3. 根据这些规则的结果启用或禁用提交按钮。

SwiftUI 通过将状态外化为类中的属性,并使用 ObservableObject 协议将该类引用到模型中来实现此目的。 * 两个属性 firstEntrysecondEntry 作为字符串使用 @Published 属性包装,允许 SwiftUI 绑定到它们的更新,以及更新它们。

  • 第三个属性 submitAllowed 暴露为 Combine 发布者,可在视图内使用,从而维护视图内部的 @State buttonIsDisabled 状态
  • 第四个属性 —— 一个 validationMessages 字符串数组 - 在 Combine 管道中将前两个属性进行组合计算,并且使用 @Published 属性包装暴露给 SwiftUI。

SwiftUI-Notes/ReactiveFormModel.swift

import Foundation
import Combine

class ReactiveFormModel : ObservableObject {

    @Published var firstEntry: String = "" {
        didSet {
            firstEntryPublisher.send(self.firstEntry)  // 1
        }
    }
    private let firstEntryPublisher = CurrentValueSubject<String, Never>("")  // 2

    @Published var secondEntry: String = "" {
        didSet {
            secondEntryPublisher.send(self.secondEntry)
        }
    }
    private let secondEntryPublisher = CurrentValueSubject<String, Never>("")

    @Published var validationMessages = [String]()
    private var cancellableSet: Set<AnyCancellable> = []

    var submitAllowed: AnyPublisher<Bool, Never>

    init() {

        let validationPipeline = Publishers.CombineLatest(firstEntryPublisher, secondEntryPublisher)  // 3
            .map { (arg) -> [String] in  // 4
                var diagMsgs = [String]()
                let (value, value_repeat) = arg
                if !(value_repeat == value) {
                    diagMsgs.append("Values for fields must match.")
                }
                if (value.count < 5 || value_repeat.count < 5) {
                    diagMsgs.append("Please enter values of at least 5 characters.")
                }
                return diagMsgs
            }

        submitAllowed = validationPipeline  // 5
            .map { stringArray in
                return stringArray.count < 1
            }
            .eraseToAnyPublisher()

        let _ = validationPipeline  // 6
            .assign(to: \.validationMessages, on: self)
            .store(in: &cancellableSet)
    }
}
  1. firstEntrysecondEntry 都使用空字符串作为默认值。
  2. 然后,这些属性还用 currentValueSubject 进行镜像,该镜像属性使用来自每个 @Published 属性的 didSet 发送更新事件。这驱动下面定义的 Combine 管道,以便在值从 SwiftUI 视图更改时触发响应式更新。
  3. combineLatest 用于合并来自 firstEntrysecondEntry 的更新,以便从任一来源来触发更新。
  4. map 接受输入值并使用它们来确定和发布验证过的消息数组。该数据流 validationPipeline 是两个后续管道的发布源。
  5. 第一个后续管道使用验证过的消息数组来确定一个 truefalse 的布尔值发布者,用于启用或禁用提交按钮。
  6. 第二个后续管道接受验证过的消息数组,并更新持有的该 Observed在这里插入代码片Object 实例的 validationMessages,以便 SwiftUI 在需要时监听和使用它。

两种不同的状态更新的暴露方法 —— 作为发布者或外部状态,在示例中都进行了展示,以便于你可以更好的利用任一种方法。 提交按钮启用/禁用的选项可作为 @Published 属性进行暴露,验证消息的数组可作为 类型的发布者而对外暴露。 如果需要涉及作为显式状态去跟踪用户行为,则通过暴露 @Published 属性可能更清晰、不直接耦合,但任一种机制都是可以使用的。

上述模型与声明式地使用外部状态的 SwiftUI 视图相耦合。

SwiftUI-Notes/ReactiveForm.swift

import SwiftUI

struct ReactiveForm: View {

    @ObservedObject var model: ReactiveFormModel  // 1
    // $model is a ObservedObject.Wrapper
    // and $model.objectWillChange is a Binding
    @State private var buttonIsDisabled = true  // 2
    // $buttonIsDisabled is a Binding

    var body: some View {
        VStack {
            Text("Reactive Form")
                .font(.headline)

            Form {
                TextField("first entry", text: $model.firstEntry)  // 3
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .lineLimit(1)
                    .multilineTextAlignment(.center)
                    .padding()

                TextField("second entry", text: $model.secondEntry) // 4
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .multilineTextAlignment(.center)
                    .padding()

                VStack {
                    ForEach(model.validationMessages, id: \.self) { msg in  // 4
                        Text(msg)
                            .foregroundColor(.red)
                            .font(.callout)
                    }
                }
            }

            Button(action: {}) {
                Text("Submit")
            }.disabled(buttonIsDisabled)
                .onReceive(model.submitAllowed) { submitAllowed in  // 5
                    self.buttonIsDisabled = !submitAllowed
            }
            .padding()
            .background(RoundedRectangle(cornerRadius: 10)
                .stroke(Color.blue, lineWidth: 1)
            )

            Spacer()
        }
    }
}

struct ReactiveForm_Previews: PreviewProvider {
    static var previews: some View {
        ReactiveForm(model: ReactiveFormModel())
    }
}
  1. 数据模型使用 @ObservedObject 暴露给 SwiftUI。
  2. @State buttonIsDisabled 在该视图中被声明为局部变量,有一个默认值 true
  3. 属性包装($model.firstEntry$model.secondEntry) 的预计值用于将绑定传递到 TextField 视图元素。当用户更改值时,Binding 将触发引用模型上的更新,并让 SwiftUI 的组件知道,如果暴露的模型正在被更改,则组件的更改也即将发生。
  4. 在数据模型中生成和 assign 的验证消息,作为 Combine 管道的发布者,在这儿对于 SwiftUI 是不可见的。相反,这只能对这些被暴露的值的变化所引起的模型的变化做出反应,而不关心改变这些值的机制。
  5. 作为如何使用带有 onReceive 的发布者的示例,使用 onReceive 订阅者来监听引用模型中暴露的发布者。在这个例子中,我们接受值并把它们作为局部变量 @State 存储在 SwiftUI 的视图中,但它也可以在一些转化后使用,如果该逻辑只和视图显示的结果值强相关的话。在这,我们将其与 Button 上的 disabled 一起使用,使 SwiftUI 能够根据 @State 中存储的值启用或禁用该 UI 元素。

参考

https://heckj.github.io/swiftui-notes/index_zh-CN.html

代码

https://github.com/heckj/swiftui-notes

你可能感兴趣的:(iOS,swift,swiftui,服务器)