取消表单更新时恢复初始值,如何优化SwiftUI现有实现?
优雅实现SwiftUI中Book对象的Cancel恢复功能
问题核心
当前代码需要逐个保存每个字段的初始值,字段数量增多时会变得繁琐。尝试直接把整个Book对象存为@State后赋值会报错,因为@Bindable修饰的book是SwiftData管理的不可变引用,无法直接替换整个实例。
解决方案
通过深拷贝初始Book对象,用@State保存这个副本,Cancel时将副本的属性批量赋值给原对象,无需逐个处理字段。
步骤1:给Book添加拷贝初始化方法
SwiftData的@Model类默认没有深拷贝功能,手动实现拷贝初始化器:
@Model class Book { var title: String var author: String init(title: String, author: String) { self.title = title self.author = author } // 拷贝初始化器,复制所有字段 init(from book: Book) { self.title = book.title self.author = book.author } }
步骤2:修改BookDetail视图,统一保存初始对象
不再需要逐个存储字段,直接用@State保存整个初始对象的拷贝:
struct BookDetail: View { @Bindable var book: Book @State private var initialBook: Book? // 保存初始状态的拷贝 @Environment(\.dismiss) private var dismiss var body: some View { Form { TextField("Title", text: $book.title, axis: .vertical) TextField("Author", text: $book.author, axis: .vertical) } .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Save") { dismiss() } } ToolbarItem(placement: .cancellationAction) { Button("Cancel") { restoreBook() dismiss() } } } .onAppear { // 视图加载时创建初始拷贝 if initialBook == nil { initialBook = Book(from: book) } } } func restoreBook() { guard let initialBook = initialBook else { return } // 批量赋值所有属性,后续新增字段只需修改拷贝初始化器 book.title = initialBook.title book.author = initialBook.author } }
进阶优化:用反射自动处理多字段
如果字段数量极多,不想手动写每个属性的赋值,可以用Swift的Mirror反射机制自动遍历赋值:
func restoreBook() { guard let initialBook = initialBook else { return } let initialMirror = Mirror(reflecting: initialBook) let bookMirror = Mirror(reflecting: book) // 遍历初始对象属性,自动赋值给原对象 for (label, value) in initialMirror.children { guard let label = label, let bookChild = bookMirror.children.first(where: { $0.label == label }), let keyPath = bookChild.value as? AnyMutableProperty else { continue } keyPath.set(value) } } // 辅助协议,支持反射修改属性 protocol AnyMutableProperty { func set(_ value: Any) } extension WritableKeyPath: AnyMutableProperty where Root: NSObject { func set(_ value: Any) { guard let typedValue = value as? Value else { return } (root as NSObject)[keyPath: self] = typedValue } }
注意:反射机制存在轻微性能开销,适合字段极多的场景,普通场景手动拷贝更简洁可靠。
关键说明
为什么不能直接替换整个book对象?因为@Bindable var book: Book中的book是SwiftData管理的对象引用,SwiftData需要追踪对象的属性变化而非实例替换,因此只能修改对象的属性值,不能直接替换整个实例。
内容的提问来源于stack exchange,提问作者Edward
相关产品推荐
相关产品推荐

