Binding 将SwiftUI视图绑定到领域对象的意外行为
我正在为我的应用程序开发UI,它使用领域作为后端。文本由用户在自定义文本字段中输入,该字段使用“浮动”标签以有效利用屏幕。标签动画(由非常简单的逻辑支持)是通过检查领域对象和自定义视图的属性之间的绑定状态触发的 当我对视图进行原型设计时,我使用了由结构支持的@State或@Binding属性,并且视图的行为完全符合预期。然而,当在我的主应用程序中使用它时,@State或@Binding会包装一个类(一个领域对象),并且转换不会被触发,当然,这是因为视图没有在其内部状态中注册更改,因此它不会随着颜色和偏移量的更改而重新渲染 当我意识到在处理类时应该使用@observeObject时,我认为我已经找到了解决方案,但这也不起作用。仔细想想,这似乎是有道理的,尽管我对各种属性包装器的工作方式有点困惑 我非常怀疑这有一个解决方法,我的第一个调用端口可能会挂接到willChange以修改必要的状态。但是,同时,有人能解释一下这里发生了什么,因为我有点迷糊。如果你手头碰巧有个解决方案,这可能会阻止我去做一些疯狂的切线 自定义视图:Binding 将SwiftUI视图绑定到领域对象的意外行为,binding,swiftui,property-wrapper,Binding,Swiftui,Property Wrapper,我正在为我的应用程序开发UI,它使用领域作为后端。文本由用户在自定义文本字段中输入,该字段使用“浮动”标签以有效利用屏幕。标签动画(由非常简单的逻辑支持)是通过检查领域对象和自定义视图的属性之间的绑定状态触发的 当我对视图进行原型设计时,我使用了由结构支持的@State或@Binding属性,并且视图的行为完全符合预期。然而,当在我的主应用程序中使用它时,@State或@Binding会包装一个类(一个领域对象),并且转换不会被触发,当然,这是因为视图没有在其内部状态中注册更改,因此它不会随着颜
struct FloatingLabelTextField: View {
let title: String
let text: Binding<String>
let keyboardType: UIKeyboardType?
let contentType: UITextContentType?
var body: some View {
ZStack(alignment: .leading) {
Text(title)
.foregroundColor(text.wrappedValue.isEmpty ? Color(.placeholderText) : .accentColor)
.offset(y:text.wrappedValue.isEmpty ? 0 : -25)
.scaleEffect(text.wrappedValue.isEmpty ? 1 : 0.8, anchor: .leading)
TextField("", text: text, onEditingChanged: { _ in print("Edit change") }, onCommit: { print("Commit") })
.keyboardType(keyboardType ?? .default)
.textContentType(contentType ?? .none)
}
.padding(15)
.border(Color(.placeholderText), width: 1)
.animation(.easeIn(duration: 0.3))
}
}
或者
对对象使用@State/@绑定:
class TestData: ObservableObject {
var name: String = ""
}
struct ContentView: View {
@ObservedObject private var test = TestData()
var body: some View {
VStack {
FloatingLabelTextField(title: "Test", text: $test.name, keyboardType: nil, contentType: nil)
}
}
添加了丢失的@Published并带回对象(发布原始代码以尽可能简单的方式显示问题) 以上崩溃,错误如下: 如上面的注释所述,在第一次创建对象时,标准做法是在客户端验证完成之前不保存到领域(从而使其处于管理状态)。看起来需要一种不同的方法 更新: 解决方案1-仅在填充数据字段后创建对象 我使用Realm的SwiftUI示例代码汇总了以下内容。这是非常混乱的,因为我没有跟上新的SwiftUI@App wrappers等的速度,但是UI按照预期工作(我不再绑定UI和对象,而是依赖@State属性),并且我已经确认域存储正在正确更新
@main
struct TestApp: App {
var body: some Scene {
let realm = try! Realm()
var dogs = realm.object(ofType: Dogs.self, forPrimaryKey: 0)
if dogs == nil {
dogs = try! realm.write { realm.create(Dogs.self, value: []) }
}
return WindowGroup {
ContentView(dogs: dogs!.allDogs)
}
}
}
领域对象:
final class Dog: Object {
@objc dynamic var id: String = UUID().uuidString
@objc dynamic var name: String = ""
}
final class Dogs: Object {
@objc dynamic var id: Int = 0
// all data objects stored in the realm
let allDogs = RealmSwift.List<Dog>()
override class func primaryKey() -> String? {
"id"
}
}
最终类狗:对象{
@objc动态变量id:String=UUID().UUIString
@objc动态变量名称:String=“”
}
最后一类狗:对象{
@objc动态变量id:Int=0
//存储在域中的所有数据对象
让allDogs=RealmSwift.List()
重写类func primaryKey()->字符串{
“id”
}
}
主要观点:
struct ContentView: View {
let dogs: RealmSwift.List<Dog>
// MARK: UI state
@State private var name = ""
var body: some View {
Form {
FloatingLabelTextField(title: "Name", text: $name, keyboardType: nil, contentType: nil)
Button("Commit") {
var dog = Dog()
dog.name = name
self.dogs.realm?.beginWrite()
self.dogs.append(dog)
try! self.dogs.realm?.commitWrite()
self.name = ""
}
}
}
}
struct ContentView:View{
放狗:RealmSwift.列表
//标记:UI状态
@国家私有变量名称=“”
var body:一些观点{
形式{
FloatingLabelTextField(标题:“名称”,文本:$Name,键盘类型:nil,内容类型:nil)
按钮(“提交”){
var dog=dog()
dog.name=名称
self.dogs.realm?.beginWrite()
self.dogs.append(dog)
试试!self.dogs.realm?.commitWrite()
self.name=“”
}
}
}
}
解决方案2(不成功):
我试图通过遵循错误消息(领域对象需要由领域管理以允许更改通知)来解决问题,但没有成功。从头开始创建新对象时,必须将属性上设置了临时值的“空白”版本写入域,然后写入用于编辑属性的文本字段,并在单独的事务中将更改写回域
因为在SwiftUI中使用领域比在UIKit中使用领域要复杂一些(新对象是通过主列表对象编写的,而不是独立编写的),这很快就变得复杂了,所以我决定不值得花费时间和精力,因为第一个解决方案似乎还可以。当然,我对其他人的观点(或解决方案)非常感兴趣,如果他们有…我相信你只需要添加一些缺失的部分
class TestData: ObservableObject {
@Published var name: String = ""
}
这不是一个答案-用这些信息更新您的问题。添加了一个有效的解决方案-查看另一个简短的解决方案,以纠正原始问题(正如Asperi已经指出的那样)(但是@Published不能用于领域对象,除非对其进行管理(请参阅我的附加代码).就像通常的情况一样,我解决了一个问题,然后又直接遇到了另一个问题:-)
final class Dog: Object {
@objc dynamic var id: String = UUID().uuidString
@objc dynamic var name: String = ""
}
final class Dogs: Object {
@objc dynamic var id: Int = 0
// all data objects stored in the realm
let allDogs = RealmSwift.List<Dog>()
override class func primaryKey() -> String? {
"id"
}
}
struct ContentView: View {
let dogs: RealmSwift.List<Dog>
// MARK: UI state
@State private var name = ""
var body: some View {
Form {
FloatingLabelTextField(title: "Name", text: $name, keyboardType: nil, contentType: nil)
Button("Commit") {
var dog = Dog()
dog.name = name
self.dogs.realm?.beginWrite()
self.dogs.append(dog)
try! self.dogs.realm?.commitWrite()
self.name = ""
}
}
}
}
class TestData: ObservableObject {
@Published var name: String = ""
}