How does one handle optional propertywrappers

I have a propertywrapper that looks like the one shown. How does one call an optional propertywrapper when trying to pass it in as a parameter for a View that also wants to use a Binding? I tried self.$comment?.text but that is not accepted.

@Binding var comment: Comment?

What does the View expect of the argument? Does it want Binding<Text?>, Binding<Text>?, Text?, or something else?

The view wants Binding. What I would like to do is something like this below. However compiler does not like self.$comment.text, since comment is an optional

MyView(text: self.comment != nil ? self.$comment.text : Binding.constant(""))

Unfortunately, dynamic member lookup, which this Binding feature is based on, doesn't support optional chaining. So you need to write it out explicitly.

Before we do that, there are assumptions baked in there (assuming your code "works") that I want to point out.

  • What happens if comment becomes non-nil after MyView is created?
    • Your code says MyView will still use the constant binding.
  • What happens if comment becomes nil after MyView is created?
    • I'm not sure your code would even make sense in that case since $comment.text would at-best be Binding<String?>, not Binding<String>.

Now I believe you're instead trying to express this:

Binding(
  get: { comment?.text ?? "" }
  set: { comment?.text = $0 }
)

which would check for nil every time it is accessed.

2 Likes

This is beautiful and should work. Thank you.

Some more questions:

  1. Can I use this Binding syntax with non-property wrappers? If so when would I need to use property wrappers as opposed to this Binding syntax.
  2. I assume these get and set parameters are escaping because the lifetime of the view they are passed to may be longer than the source of the binding?

It's a "simple" initializer, so it works everywhere. About which one to choose, I think it boils down to convenience. If I can write $value.data1.data2, I wouldn't want to write down:

Binding(
  get: { value.data1.data2 },
  set: { value.data1.data2 = $0 }
)

That is correct (and note the @escaping in the declaration).

My understanding is that view creating MyView in its body will depend on the comment binding. And when comment changes between nil/non-nil, body should be called again, producing new value of MyView.

So, original code should still work, at least for the reading. But for the writing it will not work. If we don’t need writing then there is no need for binding at all. Seeing Binding, I assume that MyView may use it to modify the value. So we still need @Lantua’s approach, but for the different reasoning.

1 Like