I've developing the UI for my app which uses Realm for the backend. Text is entered by the user in a customised TextField that uses a "floating" label to make efficient use of the screen. The label animation (backed by very simple logic) is triggered by checking the state of a Binding between the properties of the Realm object and the custom View.
When I was prototyping the view I used a @State or @Binding property backed by a struct and the view behaves exactly as expected. However, when using this in my main app, @State or @Binding wrap a class (a Realm Object) and the transition is never triggered, certainly because the view doesn't register the change in its internal state so it isn't re-rendered with the changes in colour and offset.
I thought I had the solution when I realised that I should be using @ObservableObject when working with classes but this doesn't work either. Thinking it through, this seems to make sense although I get a bit confused about how the various property wrappers are working.
I'm very much suspect there's a workaround for this and my first port of call is likely to hooking into willChange to modify the necessary state. But, in the meantime, could someone explain what is going on here as I'm a bit hazy. If you happen to have a solution to hand, this might stop me going off on some mad tangent?
Custom view:
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))
}
}
Functional implementations:
struct MyView: View {
@State private var test = ""
var body: some View {
VStack {
FloatingLabelTextField(title: "Test", text: $test, keyboardType: nil, contentType: nil)
}
}
}
or...
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)
}
}
Using @State/@Binding with an object:
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)
}
}