You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 14:51:05