如何响应嵌套类中计算属性变化,通过Combine触发UI重新渲染?
实现方案
要实现计算属性isComplete变化时自动更新UI,以及UI输入到模型的反向绑定,按以下步骤改造即可:
核心改造点
- 让
PersonName和ViewModel遵守Combine的ObservableObject协议,获得响应式观察能力 - 在
text属性的setter中手动发送变化通知,触发所有依赖text的计算属性(包括isComplete)的更新 - 用SwiftUI的双向绑定语法直接关联UI输入控件和模型的
text属性,自动实现UI到模型的同步
完整修改代码
首先确保导入Combine框架:
import Combine import SwiftUI
改造PersonName类
extension EditPersonView.ViewModel { // 遵守ObservableObject协议 final class PersonName: ObservableObject { let person: Person private let formatter = PersonNameComponentsFormatter() init(for person: Person) { self.person = person } // 计算属性手动发送变化通知,替代@Published(因为@Published仅支持存储属性) var text: String { get { person.name ?? "" } set { objectWillChange.send() person.name = newValue } } // 原有逻辑保持不变 private var components: PersonNameComponents? { formatter.personNameComponents(from: text) } var givenName: String? { components?.givenName } var familyName: String? { components?.familyName } private func isValid(component: String?) -> Bool { guard let name = component, name.count > 1 else { return false } return true } var elements: Set<Elements> { var collection = Set<Elements>() if isValid(component: givenName) { collection.insert(.givenName) } if isValid(component: familyName) { collection.insert(.familyName) } return collection } var isComplete: Bool { Set([.givenName, .familyName]).isSubset(of: elements) } // 可选:单独的isComplete变化Publisher,用于手动订阅做其他逻辑 var isCompletePublisher: AnyPublisher<Bool, Never> { objectWillChange .map { [weak self] _ in self?.isComplete ?? false } .removeDuplicates() // 仅在值实际变化时发送信号,减少不必要的更新 .eraseToAnyPublisher() } } }
改造ViewModel类
extension EditPersonView { // 遵守ObservableObject协议 final class ViewModel: ObservableObject { let name: PersonName init(person: Person) { self.name = PersonName(for: person) } } }
改造View层实现双向绑定
struct EditPersonView: View { // 用@StateObject持有ViewModel,保证生命周期正确 @StateObject var model: ViewModel // 你原有逻辑的submitted状态 @State private var submitted = false private var captionView: some View { HStack { /* 原有其他逻辑 */ if submitted && model.name.isComplete { Spacer() Text("select".localizedCapitalized) + Text(" ") + Text("save") + Text(" ") + Text("👆") } } } var body: some View { VStack { // 双向绑定:UI输入自动同步到model.text,model变化自动更新UI显示 TextField("请输入姓名", text: $model.name.text) .textFieldStyle(.roundedBorder) captionView } .padding() // 可选:手动订阅isComplete变化做其他操作(比如弹提示、修改其他状态) .onReceive(model.name.isCompletePublisher) { isComplete in print("当前姓名填写完整性:", isComplete) // 在这里添加你的自定义逻辑 } } }
逻辑说明
- isComplete自动更新逻辑:
isComplete是依赖text的派生计算属性,只要text发生修改(不管是UI输入还是代码修改),就会触发objectWillChange通知,SwiftUI会自动刷新所有用到isComplete的UI部分,不需要手动更新。 - 反向绑定实现逻辑:
$model.name.text语法创建了双向绑定:- 模型的
text变化时,TextField的显示内容自动更新(模型>>>UI) - 用户在TextField输入内容时,自动同步修改模型的
text属性,进而更新到person.name(模型<<<UI)
不需要手动编写任何文本变化回调,系统已经自动处理了双向同步逻辑。
- 模型的
如果你使用iOS 17及以上版本,可以用
@Observable宏替代ObservableObject,代码会更简洁,不需要手动调用objectWillChange.send()。
内容的提问来源于stack exchange,提问作者bobby123uk
相关产品推荐
相关产品推荐

