0

我正在为我的应用程序开发 UI,它使用 Realm 作为后端。用户在自定义 TextField 中输入文本,该 TextField 使用“浮动”标签来有效利用屏幕。标签动画(由非常简单的逻辑支持)是通过检查 Realm 对象的属性和自定义 View 之间的 Binding 状态来触发的。

当我对视图进行原型设计时,我使用了由结构支持的 @State 或 @Binding 属性,并且视图的行为完全符合预期。但是,当在我的主应用程序中使用它时,@State 或 @Binding 包装一个类(一个领域对象)并且永远不会触发转换,当然是因为视图没有注册其内部状态的更改,所以它不是重新-随着颜色和偏移的变化而渲染。

当我意识到在处理类时应该使用@ObservableObject 时,我认为我有解决方案,但这也不起作用。仔细想想,这似乎是有道理的,尽管我对各种属性包装器的工作方式有点困惑。

我非常怀疑有一个解决方法,我的第一个停靠港可能会挂接到 willChange 以修改必要的状态。但是,与此同时,有人可以解释这里发生了什么,因为我有点朦胧。如果你碰巧有一个解决方案,这可能会阻止我走上一些疯狂的切线?

自定义视图:

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))
}
}

功能实现:

struct MyView: View {
@State private var test = ""

var body: some View {
    VStack {
        FloatingLabelTextField(title: "Test", text: $test, keyboardType: nil, contentType: nil)
    }
}
}

或者...

struct TestData {
    var name: String = ""
}

struct ContentView: View {
    @State private var test = TestData()
    
    var body: some View {
        VStack {
            FloatingLabelTextField(title: "Test", text: $test.name, keyboardType: nil, contentType: nil)
    }
}

带结构

将@State/@Binding 与对象一起使用:

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)
    }
}

使用对象

4

2 回答 2

1

添加了缺少的 @Published 并将 Object 带回(发布的原始代码以尽可能简单的术语显示问题)。

@objcMembers
class TestData: Object {
    @Published dynamic 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)
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
}

以上崩溃并出现以下错误:

错误信息

正如上面评论中提到的,当第一次创建一个对象时,标准的做法是在客户端验证完成之前不保存到领域(从而使其成为管理对象)。看起来需要一种不同的方法。

更新:

解决方案 1 - 仅在填充数据字段后创建对象。

我使用 Realm 的 SwiftUI 示例代码汇总了以下内容。这很混乱,因为我没有跟上新的 SwiftUI @App 包装器等的速度,但是 UI 可以按预期工作(我不再绑定 UI 和对象,而是依赖于 @State 属性)而且我已经确认 Realm 商店正在正确更新。

@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"
    }
}

主视图:

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 = ""
            }
        }
    }
}

解决方案 2(不成功):

我尝试通过遵循错误消息(Realm Object 需要由 Realm 管理以允许更改通知)来解决问题的尝试并不顺利。从头开始创建新对象时,必须将在属性上设置了临时值的“空白”版本写入领域,然后将用于编辑属性的文本字段写入单独的事务中将更改写回领域。

因为在 SwiftUI 中使用 Realm 比使用 UIKit 的情况要复杂一些(新对象是通过主 List 对象而不是独立编写的)这很快变得复杂,所以我认为不值得花时间和精力第一个解决方案似乎没问题。当然,如果他们有其他人的观点(或解决方案),我会对他们非常感兴趣......

于 2020-06-24T14:23:51.053 回答
0

我相信你只需要添加一些缺失的部分。

class TestData: ObservableObject {
    @Published var name: String = ""
 }
于 2020-06-24T14:31:23.570 回答