Default init internal needs a workaround. Enough is enough

You can re-initialize a View's @State by changing the view's id. I'm not sure this work around is kosher or just happens to work now and will break in the future.

.init(initialValue:) vs. .init(wrappedValue)

What, then, is the correct way to create a public initializer for a public view whose state variables are private and where the caller would like to specify an initial value for those variables?

Or are you saying that because they are private, the caller shouldn't know about them, the initializer shouldn't expose them, and that they should always have a default value? And if the caller really does need to know about them and supply a value that they should @Bindings instead of @State?

Multiple SwiftUI views have multiple initializers for multiple purposes. How do I create my own view that has a decent API with multiple initializers and accepts a BINDING in one case and no binding in another? I don't want to write self._binding = binding because it is obviously not an officially correct way, it is underscored.

Using _variable for wrapper storage is official, though.

Must have missed that. But why would anyone assume that doing something with an auto-generated underscored variable is safe?

I'm pretty sure he just meant that if they're gonna take the initial value from outside, each call needs to have the same initial value.

Which part of it is unsafe, though? We pretty much know exactly how the wrapper code is generated, and it (officially) uses underscored name.

Are you sure about that? I never even considered using anything like that. I would guess most people are like me and are too wary of SwiftUI magic to use things in a radically different way from official recommendations.

My point was that there is no official recommendation or anything preventing the use of the initializer like that, so that's what most users will reach for by default. Apple has no obvious examples of initializing a View with initial @State values so unless they do their own research there's no way for them to figure out that @State in SwiftUI is different than other wrappers you might want to initialize.

And I have only seen examples where @State variables have initial values! Like:

@State private var isActive = false

That obviously not what we're talking about here. The question is, how do you provide an initial value to that state from outside the view? Naturally users would reach for the initializer, but that's apparently not what you're supposed to do. That realization is both non obvious and not really documented.

My impression was that you're not supposed to use it like that, that State properties should only be used internally and if you want to pass in data you use a normal let property, or a Binding if you want to change it.

I guess this doesn't really cover the case where you want to get a starting value from the outside, but don't want to bother the outside with any subsequent changes to the value, but for me it would still be more natural to use a binding then. Or simply update the State properties value in the initialiser based on the input (does that work?)

That can work, if the views are directly related. But you still have the non-obvious nature of binding state you don't care about in the parent view. However, once there's more than a single layer between the state and the source of the initial value, it gets especially burdensome to constantly bind and rebind. For instance, I want to provide an initial value to a Picker based on the button that was tapped to navigate to the screen where the Picker is a child view. Outside of the Picker I don't care what the selection is, I just want to provide an initial value. It seems natural to reach for an initializer to provide this value, rather than having to set up a Binding or ObservableObject for that sole purpose.

1 Like

Hm, but wouldn't you normally want to use the picker value somewhere? Isn't the beauty of SwiftUI that you can keep everything in sync all the time, automatically. In such cases I would have a sort of Coordinator as an environment object with a property that you bind directly to the picker.

Perhaps in this case the initial value is separate from the picked value? If so, can't you just pass it in through a var/let property, since you are not going to change it?

It's a SegmentedPickerStyle() picker which reveals one of two views, so it doesn't matter what the selection is, just that we can set it to one of the two options.

Could you post a snippet? It still seems to me you should be able to init your view with the initial value as a normal property.

SomeView: View {
    let someInitialValue: Int // or whatever
    var body: some View {
        Picker( // use someInitialValue ...)

// Elsewhere
SomeView(someInitialValue: 5)

There's no way to initialize a Picker with initial state, only a binding to its selection. Hence this thread.

If the Picker only takes a binding, and that is used both for the initial value and the state, then I really don't see what the problem is with just passing in a Binding from the owning view, where it could be a State or Published or something else.

I can understand that in theory there are cases where you might want to set the initial value of a state from the outside but this doesn't seem to be one of those cases. Maybe I'm just misunderstanding, because I also don't understand why you don't care about the selection.

This has all been covered in the thread, you should give it a read.

1 Like

I just re-read it and I still can't see exactly what you or OP are trying to achieve, only that he wanted to init some State vars manually, which is the wrong solution. If you would state your actual issue, people could help you find acceptable solutions to it. And I certainly can't see, from your description, why you can't just set your initial value using the binding.

If you want to pass in the initial value from the owning view, then presumably you could just make that data a State property there? Or, if it's something of a more global character, use an observed object with a Published property and put that in the environment, as described here:

I haven't found any cases so far that aren't covered by these approaches.