Combine 系列
- Swift Combine 从入门到精通一
- Swift Combine 发布者订阅者操作者 从入门到精通二
- Swift Combine 管道 从入门到精通三
- Swift Combine 发布者publisher的生命周期 从入门到精通四
- Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
- Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
- Swift 使用 Combine 进行开发 从入门到精通七
- Swift 使用 Combine 管道和线程进行开发 从入门到精通八
- Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
- Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
- Swift Combine 用 Future 来封装异步请求 从入门到精通十一
- Swift Combine 有序的异步操作 从入门到精通十二
- Swift Combine 使用 flatMap 和 catch错误处理 从入门到精通十三
- Swift Combine 网络受限时从备用 URL 请求数据 从入门到精通十四
- Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五
- Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六
1. 合并多个管道以更新 UI 元素
目的:观察并响应多个 UI 元素发送的值,并将更新的值联合起来以更新界面。
此示例故意模仿许多 Web 表单样式的验证场景,不过是在 UIKit 中使用 Combine。
ViewController 被配置了多个通过声明式更新的元素。 同时持有了 3 个主要的文本输入字段:
- value1
- value2
- value2_repeat
它还有一个按钮来提交合并的值,以及两个 labels 来提供反馈。
这些字段的更新规则被实现为:
- value1 中的条目至少有 3 个字符。
- value2 中的条目至少有 5 个字符。
- value2_repeat 中的条目必须与 value2 相同。
如果这些规则中的任何一个未得到满足,则我们希望禁用提交按钮并显示相关消息,解释需要满足的内容。
这可以通过设置连接与合并在一起的一系列管道来实现。
- 有一个
@Published
属性匹配每个用户输入字段。combineLatest
用于从属性中获取不断发布的更新,并将它们合并到单个管道中。map
操作符强制执行所需字符和值必须相同的规则。 如果值与所需的输出不匹配,我们将在管道中传递 nil。 - value1 还另外有一个验证管道,只使用了
map
操作符来验证值,或返回 nil。 - 执行验证的
map
操作符内部的逻辑也用于更新用户界面中的 label 信息。 - 最终管道使用
combineLatest
将两条验证管道合并为一条管道。 此组合的管道上连接了订阅者,以确定是否应启用提交按钮。
下面的示例将这些结合起来进行了展示。
UIKit-Combine/FormViewController.swift
import UIKit
import Combine
class FormViewController: UIViewController {
@IBOutlet weak var value1_input: UITextField!
@IBOutlet weak var value2_input: UITextField!
@IBOutlet weak var value2_repeat_input: UITextField!
@IBOutlet weak var submission_button: UIButton!
@IBOutlet weak var value1_message_label: UILabel!
@IBOutlet weak var value2_message_label: UILabel!
@IBAction func value1_updated(_ sender: UITextField) { // 1
value1 = sender.text ?? ""
}
@IBAction func value2_updated(_ sender: UITextField) {
value2 = sender.text ?? ""
}
@IBAction func value2_repeat_updated(_ sender: UITextField) {
value2_repeat = sender.text ?? ""
}
@Published var value1: String = ""
@Published var value2: String = ""
@Published var value2_repeat: String = ""
var validatedValue1: AnyPublisher<String?, Never> { // 2
return $value1.map { value1 in
guard value1.count > 2 else {
DispatchQueue.main.async { // 3
self.value1_message_label.text = "minimum of 3 characters required"
}
return nil
}
DispatchQueue.main.async {
self.value1_message_label.text = ""
}
return value1
}.eraseToAnyPublisher()
}
var validatedValue2: AnyPublisher<String?, Never> { // 4
return Publishers.CombineLatest($value2, $value2_repeat)
.receive(on: RunLoop.main) // 5
.map { value2, value2_repeat in
guard value2_repeat == value2, value2.count > 4 else {
self.value2_message_label.text = "values must match and have at least 5 characters"
return nil
}
self.value2_message_label.text = ""
return value2
}.eraseToAnyPublisher()
}
var readyToSubmit: AnyPublisher<(String, String)?, Never> { // 6
return Publishers.CombineLatest(validatedValue2, validatedValue1)
.map { value2, value1 in
guard let realValue2 = value2, let realValue1 = value1 else {
return nil
}
return (realValue2, realValue1)
}
.eraseToAnyPublisher()
}
private var cancellableSet: Set<AnyCancellable> = [] // 7
override func viewDidLoad() {
super.viewDidLoad()
self.readyToSubmit
.map { $0 != nil } // 8
.receive(on: RunLoop.main)
.assign(to: \.isEnabled, on: submission_button)
.store(in: &cancellableSet) // 9
}
}
- 此代码的开头遵照了 通过用户输入更新声明式 UI 中的模式. IBAction 消息用于更新
@Published
属性,触发对所连接的任何订阅者的更新。 - 第一个验证管道使用
map
操作符接收字符串值输入,如果与验证规则不符,则将其转换为nil
。 这也将发布者属性的输出类型从<String>
转换为可选的<String?>
。 同样的逻辑也用于触发消息文本的更新,以提供有关所需内容的信息。 - 由于我们正在更新用户界面元素,因此我们明确将这些更新包裹在
DispatchQueue.main.async
中,以在主线程上调用。 combineLatest
将两个发布者合并到一个管道中,该管道的输出类型是每个上游发布者的合并值。 在这个例子中,输出类型是(<String>, <String>)
的元组。- 与其使用
DispatchQueue.main.async
,不如使用receive
操作符明确在主线程上执行下一个操作符,因为它将执行 UI 更新。 - 两条验证管道通过
combineLatest
相结合,并将经过检查的输出合并为单个元组输出。 - 我们可以将分配的管道存储为
AnyCancellable?
引用(将其映射到 viewcontroller 的生命周期),但另一种选择是创建一个变量来收集所有可取消的引用。 这从空集合开始,任何 sink 或 assign 的订阅者都可以被添加到其中,以持有对它们的引用,以便他们在 viewcontroller 的整个生命周期内运行。 如果你正在创建多个管道,这可能是保持对所有管道的引用的便捷方式。 - 如果任何值为 nil,则
map
操作符将向管道传递 false 值。 对 nil 值的检查提供了用于启用(或禁用)提交按钮的布尔值。 - store 方法可在 Cancellable 协议上调用,该协议明确设置为支持存储可用于取消管道的引用。
在这里插入代码片
参考
https://heckj.github.io/swiftui-notes/index_zh-CN.html
代码
https://github.com/heckj/swiftui-notes