- 目的
-
-
SwiftUI 包含 @ObservedObject 和 ObservableObject 协议,它为 SwiftUI 的视图提供了将状态外部化的手段,同时通知 SwiftUI 模型的变化。
-
- 参考
- 另请参阅
-
SwiftUI 的例子:
- 代码和解释
-
SwiftUI 视图是基于某些已知状态呈现的声明性结构,当该状态发生变化时,这些当前的结构将失效并更新。 我们可以使用 Combine 来提供响应式更新来操纵此状态,并将其暴露回 SwiftUI。 此处提供的示例是一个简单的输入表单,目的是根据对两个字段的输入提供响应式和动态的反馈。
以下规则被编码到 Combine 的管道中: 1. 两个字段必须相同 - 如输入密码或电子邮件地址,然后通过第二个条目进行确认。 2. 输入的值至少为 5 个字符的长度。 3. 根据这些规则的结果启用或禁用提交按钮。
SwiftUI 通过将状态外化为类中的属性,并使用 ObservableObject
协议将该类引用到模型中来实现此目的。
两个属性 firstEntry
和 secondEntry
作为字符串使用 @Published 属性包装,允许 SwiftUI 绑定到它们的更新,以及更新它们。
第三个属性 submitAllowed
暴露为 Combine 发布者,可在视图内使用,从而维护视图内部的 @State buttonIsDisabled
状态。
第四个属性 —— 一个 validationMessages
字符串数组 - 在 Combine 管道中将前两个属性进行组合计算,并且使用 @Published 属性包装暴露给 SwiftUI。
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)
}
}
-
firstEntry 和 secondEntry 都使用空字符串作为默认值。
-
然后,这些属性还用 currentValueSubject 进行镜像,该镜像属性使用来自每个
@Published
属性的didSet
发送更新事件。这驱动下面定义的 Combine 管道,以便在值从 SwiftUI 视图更改时触发响应式更新。 -
combineLatest 用于合并来自
firstEntry
或secondEntry
的更新,以便从任一来源来触发更新。 -
reference.adoc 接受输入值并使用它们来确定和发布验证过的消息数组。该数据流
validationPipeline
是两个后续管道的发布源。 -
第一个后续管道使用验证过的消息数组来确定一个 true 或 false 的布尔值发布者,用于启用或禁用提交按钮。
-
第二个后续管道接受验证过的消息数组,并更新持有的该 ObservedObject 实例的
validationMessages
,以便 SwiftUI 在需要时监听和使用它。
两种不同的状态更新的暴露方法 —— 作为发布者或外部状态,在示例中都进行了展示,以便于你可以更好的利用任一种方法。
提交按钮启用/禁用的选项可作为 @Published
属性进行暴露,验证消息的数组可作为 <String[], Never>
类型的发布者而对外暴露。
如果需要涉及作为显式状态去跟踪用户行为,则通过暴露 @Published
属性可能更清晰、不直接耦合,但任一种机制都是可以使用的。
上述模型与声明式地使用外部状态的 SwiftUI 视图相耦合。
import SwiftUI
struct ReactiveForm: View {
@ObservedObject var model: ReactiveFormModel (1)
// $model is a ObservedObject<ExampleModel>.Wrapper
// and $model.objectWillChange is a Binding<ObservableObjectPublisher>
@State private var buttonIsDisabled = true (2)
// $buttonIsDisabled is a Binding<Bool>
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)
.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())
}
}
-
数据模型使用
@ObservedObject
暴露给 SwiftUI。 -
@State
buttonIsDisabled 在该视图中被声明为局部变量,有一个默认值true
。 -
属性包装(
$model.firstEntry
和$model.secondEntry
) 的预计值用于将绑定传递到 TextField 视图元素。当用户更改值时,Binding
将触发引用模型上的更新,并让 SwiftUI 的组件知道,如果暴露的模型正在被更改,则组件的更改也即将发生。 -
在数据模型中生成和 assign 的验证消息,作为 Combine 管道的发布者,在这儿对于 SwiftUI 是不可见的。相反,这只能对这些被暴露的值的变化所引起的模型的变化做出反应,而不关心改变这些值的机制。
-
作为如何使用带有 onReceive 的发布者的示例,使用
onReceive
订阅者来监听引用模型中暴露的发布者。在这个例子中,我们接受值并把它们作为局部变量@State
存储在 SwiftUI 的视图中,但它也可以在一些转化后使用,如果该逻辑只和视图显示的结果值强相关的话。在这,我们将其与Button
上的disabled
一起使用,使 SwiftUI 能够根据@State
中存储的值启用或禁用该 UI 元素。