Default init internal needs a workaround. Enough is enough

You've just moved the problem one level up the hierarchy. Providing the initial value to the State from outside the owning View is what this entire thread is about.

1 Like

But the recursion will end, the data has to be created somewhere. If that is a view somewhere above, make it a State there. If it’s a sibling view, put it on an environment object like in the link. If it goes all the way back when the app is launched, just pass it in from there, using observable object.

Is there a fourth case I can’t imagine?

1 Like

We're talking about initialization because it's the most obvious and easiest solution to the problem. That you can accomplish the equivalent with an ObservableObject from somewhere is rather besides the point (and I already mentioned it above). There's no visible reason why an initializer shouldn't work, hence the immediate digression and discussion in this thread. Add to that the fact this restriction isn't documented anywhere and you'll see this discussion over and over again.

There's also little point to continuing this discussion, as we've now looped through the topic twice. If you don't understand other's confusion by now I don't know how to explain it any clearer.

3 Likes

Sorry, I didn't realise that you were aware that there was another solution. In my mind Apple have been very clear about the usage of State, but maybe we've looked in different places.

Anyway, talking about initialisers then, it would indeed be nice with a semi-automatically synthesised intialiser, but we can get halfway there today using type inference. If you'd actually want to do what OP was trying to do, you could instead type:

public init(offerings o: [DashCarouselCellData], tradings t:[DashTradingListCellData]) {
    _offerings = .init(wrappedValue: o)
    _tradings = .init(wrappedValue: t)
}

which isn't too bad is it?

The OP proposed that solution initially, but it has been explicitly called out in this thread as an invalid usage of @State:

And, in theory, this behavior should not be relied upon and is expected to go away:

1 Like

The main argument against doing some kind of public synthesis of the implicit initializer is that your public API for your module should be explicit, and it should be something you have control over. However, the majority of Swift frameworks are internal to one app, and their APIs are totally allowed to change at a whim, since their clients are always built with it. If we did allow for some kind of public init = default or something, that caused a memberwise init to be generated with public members, we could always make it warn for resilient modules.

3 Likes

Yeah, that's a general problem with our policy around public features. @jrose has proposed in the past that we could track whether a project's dependencies are imported as version-locked or not. For version-locked modules we could be more permissive about allowing enum frozenness, memberwise struct inits, and other API decisions to leak across module boundaries, since the user has opted out of any sort of API stability for that module.

2 Likes

I know it’s not allowed, I’ve said that myself. The point was just that you don’t have to write out the type, since that seems to be the actual problem OP had.

I have one use case. Let's say we have a view that shows a number that can be incremented or decremented. One number can be selected at once. Selection is Binding provided to the view. I would like it to start with the number provided in selection.

struct Numbers: View {

    @Binding var selection: Int
    @State private var visibleNumber = 0 // This should start with the value of `selection`

    var body: some View {
        HStack {
            Button("<") {
                visibleNumber -= 1
            }
            Button("\(visibleNumber)") {
                selection = visibleNumber
            }
            .foregroundColor(visibleNumber == selection ? .accentColor : .primary)
            Button(">") {
                visibleNumber += 1
            }
        }
        .font(.title)
    }

}

visibleNumber is the number currently displayed. If I sync it with selection in onAppear, then it will reset the view each time it appears (for example in List)

        .onAppear {
            visibleNumber = selection
        }

What would be the best approach here?