SwiftUI中如何正确绑定/观察非ObservableObject对象?
问题:GRDB兼容下的SwiftUI对象绑定与观察方案
场景与限制
- 自定义
Item类需遵循GRDB要求的Codable协议,但使用@Published属性包装器会破坏自动Codable实现,因此Item无法直接遵循ObservableObject协议 - 预期效果:在
TimeEditView中,TextField输入时同步更新显示的item.name;Toggle切换时,DatePicker随item.includesTimes状态显示/隐藏
无效尝试
- 使用
@State var item: Item:Toggle切换后DatePicker无响应,状态变更无法触发视图更新 - 通过
ObservableObject的ViewModel存储@Published var item: Item并传递绑定:同样无法触发视图响应状态变化
有效但存疑的方案
参考社区方案实现自定义Observable包装类,通过@dynamicMemberLookup和手动发送变更信号实现观察:
@dynamicMemberLookup class Observable<M: AnyObject>: ObservableObject { var model: M init(_ model: M) { self.model = model } subscript<T>(dynamicMember kp: WritableKeyPath<M, T>) -> T { get { model[keyPath: kp] } set { self.objectWillChange.send() // 主动发送变更信号,触发视图更新 model[keyPath: kp] = newValue } } }
使用方式:在TimeEditView中声明@ObservedObject var item: Observable<Item>,预览时实例化Observable(Item())即可实现预期的响应式效果。
疑问与需求
- 不理解该包装类的工作原理
- 不确定此方案是否为当前场景的最优解
- 寻求更通用的适配方案,同时保证
TimeEditView的可复用性
相关代码
TimeEditView核心代码
import SwiftUI struct TimeEditView: View { @StateObject var vm = PreviewViewModel() @ObservedObject var item: Observable<Item> //@State var item: Item = Item() @Binding var selectedTime: Date @State var includesTimes: Bool = false var body: some View { NavigationStack { VStack { Text(item.name) TextField("Name", text: $item.name) Toggle("Time", isOn: $item.includesTimes) .padding() .background( RoundedRectangle(cornerRadius: 10) .fill(.ultraThinMaterial) ) if item.includesTimes { DatePicker("Time", selection: $selectedTime, displayedComponents: [.hourAndMinute]) .datePickerStyle(WheelDatePickerStyle()) .labelsHidden() .frame(maxWidth: .infinity) .background( RoundedRectangle(cornerRadius: 10) .fill(.ultraThinMaterial) ) } } .padding() .navigationTitle("Time") .navigationBarTitleDisplayMode(.inline) .presentationDetents([.medium]) .animation(.easeInOut(duration: 0.2), value: item.includesTimes) } } }
无效尝试的预览代码
尝试1:直接使用@State
#Preview { @Previewable @State var item = Item() @Previewable @State var selectedTime = Date.now TimeEditView(item: $vm.item, selectedTime: $selectedTime) }
尝试2:使用ViewModel封装@Published
final class PreviewViewModel: ObservableObject { @Published var item: Item = Item() } #Preview { @Previewable @StateObject var vm = PreviewViewModel() @Previewable @State var selectedTime = Date.now TimeEditView(item: $vm.item, selectedTime: $selectedTime) }
有效预览代码
#Preview { @Previewable @StateObject var item = Observable(Item()) @Previewable @State var selectedTime = Date.now TimeEditView(item: item, selectedTime: $selectedTime) }
内容的提问来源于stack exchange,提问作者bk24z
相关产品推荐
相关产品推荐

