didSet
/willSet
work if an "manual" binding is used:
// instead of this
Toggle("foo", isOn: $foo)
// do this
Toggle("foo", isOn: Binding(get: { foo }, set: { print("🦤"); foo = $0 }))
SwiftUI seems to not call didSet
/willSet
if the binding is from an @State
projected value. However, manual binding, @AppStorage projected value or @Published
in an ObservableObject
, didSet
/willSet
are called. Is it treating this @State $binding differently? How does it even tell which kind of binding it is?
Very strange: TextField
if the text
binding is from an @State
projected value, also no call to didSet
/willSet
, but if it's "manual" binding or ObservableObject
/@Published
projected value, didSet
/willSet
are called twice!!! per edit change because the binding's set
closure is called twice.
Stranger still: just have this TextField
, mutating the same var elsewhere didSet
/willSet
are called two extra times!
BTW: TextEditor
binding from @State projected value also not didSet
/wilSet
called, but "manual" binding and @Publish
and @AppStorage projected value binding work correctly only call didSet
/willSet
once.
Test showing what I'm talking about:
import SwiftUI
struct ContentView: View {
@State private var text = "" {
willSet {
print("\n👉👉>>> \(#function) willSet is called, text = \(text), newValue = \(newValue)")
}
didSet {
print("\n👉👉>>> \(#function) didSet is called, text = \(text), oldValue = \(oldValue)")
}
}
@State private var showA = false {
willSet {
print("\n👉👉>>> \(#function) willSet is called, showA = \(showA), newValue = \(newValue)")
}
didSet {
print("\n👉👉>>> \(#function) didSet is called, showA = \(showA), oldValue = \(oldValue)")
}
}
@State private var showB = false
@State private var showC = false
var body: some View {
VStack {
Spacer()
Text("text = \"\(text)\"")
if showA {
// didSet is not called on each edit change, not even when committed
TextField("TextA", text: $text)
.frame(height: 80)
.background(Color.green)
.onSubmit {
print("😀 committed")
}
}
if showB {
// if this kind of TextField is shown in View will cause trouble with extra set/didSet
// the set closure is called twice when `text` change, causing didSet called twice!!
// this happen for both TextField itself did the change or some place else (like the button below) cause the change
TextField("TextB", text: Binding(get: { text }, set: { print("🦆"); text = $0 }))
}
if showC {
// didSet not called on each edit change
TextEditor(text: $text)
.border(Color.mint)
// didSet is called this way correctly once per edit change
TextEditor(text: Binding(get: { text }, set: { print("🐧"); text = $0 }))
.border(Color.indigo)
}
Button("text +=") {
text += String("ABCDEFGHIJKLMNOPQRSTUVWXYZ".randomElement() ?? "⁉️")
}
Spacer()
Group {
// `didSet`/`willSet` on showA not called
Toggle("Show TextField(\"Text\", text: $text)", isOn: $showA)
// `didSet`/`willSet` on showA **are** called
Toggle("Show TextField(\"Text\", text: $text)", isOn: Binding(get: { showA }, set: { print("🦤"); showA = $0 }))
Toggle("Show TextField(\"Text\", text: Binding(...)", isOn: $showB)
Toggle("Show TextEditor(text: $text)", isOn: $showC)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}