I have a binding with optional String
as a type and in the parent view I have if condition which checks whether it is has value or not. Depending on this condition I show or hide the child view. When I make name value nil
the app is crashing, below you find code example.
class Model: ObservableObject {
@Published var name: String? = "name"
func makeNameNil() {
name = nil
}
}
struct ParentView: View {
@StateObject var viewModel = Model()
var nameBinding: Binding<String?> {
Binding {
viewModel.name
} set: { value in
viewModel.name = value
}
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Name is (viewModel.name ?? "nil")")
Button("Make name nil") {
viewModel.makeNameNil()
}
if let name = Binding(nameBinding) { /* looks like */
ChildView(selectedName: name) /* this causes the crash*/
}
}
.padding()
}
}
struct ChildView: View {
@Binding var selectedName: String
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text("Selected name: (selectedName)")
HStack {
Text("Edit:")
TextField("TF", text: $selectedName)
}
}
}
}
Here is stack of the crash.
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x107e1745c)
AG::Graph::UpdateStack::update() ()
AG::Graph::update_attribute(AG::data::ptr<AG::Node>, unsigned int) ()
AG::Subgraph::update(unsigned int) ()
Looks like a switfui bug for me, should I avoid using such constructions?
3
Answers
Thanks to @workingdogsupportUkraine and @loremipsum for help to investigate the issue.
At the moment it looks like SwiftUI bug.
There are some workarounds using a default value which I'm not happy with because in case of complex data structure it can be annoying to create placeholder instance for such purpose. I prefer another approach where we convert
Binding<Optional<Value>>
toOptional<Binding<Value>>
.You could try this alternative approach to have a binding to your optional
name: String?
.It uses
Binding<String>(...)
as shown in the code. Works for me.EDIT-1
You can of course use this example of code, closer to your original code. Since
nameBinding
is already a binding (modified now with String), havingif let name = Binding(nameBinding) ...
, that is, a binding of a binding optional, is not correct.There is an undocumented method by Apple that allows you to see how, what, when SwiftUI
View
s are loaded.If you add this to the
body
of bothView
sYou will see something like
You will notice that the child is being redrawn before the parent.
So for a split second you are trying to set a non-
Optional
String
to anOptional<String>
I would submit this as a bug report because Apple has addressed similar issues before in order to stabilize
Binding
but to address your immediate issue I would use an optional binding solution from here or a combination of both.Or a little bit different set of solutions that combines the solutions from there
with the option above if
name == ""
it will change toname == nil
with the option above if
name == ""
it will stayname == ""
andname == nil
will look likename == ""