Default init internal needs a workaround. Enough is enough

I'd make a long winded statement, but me having to write this code to use a swiftui view outside of its own framework should explain itself enough. This is ridiculous, you know it's ridiculous. I know why you guys don't want API to have implicit initializers, but enough is enough..

public struct DashboardView: View {
    //.....

    //normally this would be compiler generated, but with default 'internal' visibility
    public init(offerings o: [DashCarouselCellData], tradings t: [DashTradingListCellData]) {
        _offerings = State<[DashCarouselCellData]>(initialValue: o)
        _tradings = State<[DashTradingListCellData]>(initialValue: t)
    }

    //.....
}

It'd be so much nicer to be able to do something like

public struct DashboardView: View {
    //.....

    public init = default
    //.....

}
2 Likes

Specifically for @State, it is invalid to use it this way. The values will be installed the first time you instantiate this View, but subsequent calls with new values won't be respected.

struct DashboardContainer: View {
  @State var data: [DashCarouselCellData] = []
  var body: some View {
    VStack {
      DashboardView(offerings: data, tradings: /*...*/)
      Button("Add Data") {
        data.append(DashCarouselCellData(...))
      }
    }
  }
}

When this button is tapped, it won't propagate the change into the inner State in the DashboardView. If you're receiving input from an outer View, it's not State in the inner view. In this example, the DashboardView will always have an empty array.

1 Like

@harlanhaskins actually, that is specifically how one would manually initialize @State (it should be init(wrappedValue:), but whatever). Not that I'd recommend it, of course.


@mlfarrell Let's not start a conversation with an antagonistic tone. It's (almost) always the case that the proposer finds the current state lacking. No need to deter serious discussion.

Have you checked out other thread in this forum regarding the topic? You might find some informative about the status quo.

Consider the sequence of events:

-> DashboardView is created the first time
-> The persistent State container is created
-> Its initial value is set to the first thing passed into DashboardView's initializer

-> DashboardView is created the second time
-> The State wrapper struct that is a member of DashboardView is created and thrown away -- when body is called, a copy of the DashboardView with the value inside the persistent State is read from, which does not include the current values from the parent.

Yes, that's the SwiftUI magic. Trust me. The framework identifies the storage location from the access pattern when constructing the view hierarchy, not the object identity. State is a struct, it doesn't even have a sense of identity.

Or do you mean that the second initial value is thrown away, then yes, the initial value is discarded in subsequent creation. You should provide the same value every time.

State is a struct that wraps a reference, and reading the wrappedValue reaches into the persistent underlying reference. When DashboardView is initialized the second time, the new value is not shoved into the persistent store; it is dropped on the floor.

Ok, so you mean this:

1 Like

Yep, and the point of what I'm trying to say is that setting @State values explicitly in an initializer breaks the expectations for how data should flow through a SwiftUI hierarchy :smile:

In all seriousness, it does have a very consistent behaviour. Though most of the time you'd want to synchronize State during view events, like onAppear instead of the view initialization. So I find the manual initialization abhorrent on almost every occasion anyway.

1 Like

Any consistent behavior you see is an accident. @State's initial value must be independent of any instance of the view. Chances are good this will be enforced at compile time when the necessary property wrapper functionality exists to do so.

10 Likes

Even if it’s not the intended way to initialize @State, it’s the obvious approach most users will reach for and far lighter weight than creating some sort of ObservableObject as storage just to be able to set an initial value.

Is that documented anywhere? I just re-read some of the documentation and I couldn't find it called out explicitly. I can't tell you how times I've heard it recommended to initialize state that way. If that behavior is discouraged, it may be worth explicitly calling it out because, at least in the swath of the iOS community I've interacted with, that's a pretty widely accepted way of doing things.

2 Likes

The cases where it will appear to work are in cases where you know the view will only really be created once, e.g. in a top level of a sheet, or an “editor” view where it’ll be removed from the hierarchy after editing. If you’re relying on the fact that view won’t be re-initialized, it’s probably fine?

I’ve used this pattern in exactly these circumstances, i.e. in the top-level view in a sheet where the initial state comes from the outside, but where the state is thereafter owned and managed by the view. Why does this only ”appear to work”? What could go wrong that I’m not aware of? And what should I use instead?

wow.. just... wow. I expected to come back to a bunch of replies about.. you know.. the actual problem I posted (default init scope) and I come back to a hijacked discussion about swiftui state var behavior. Do I need to start a new post all over again? This is why swift's official github repo needs an issues section instead of this forum.

3 Likes

As I said earlier, no need to be antagonistic. Have you checked out [Review] SE-0018 Flexible Memberwise Initialization, Explicit Memberwise Initializers? Would you like to add anything to the discussion other than your apparent +1?

Even then, IIRC what missing was the implementation, not the design.

2 Likes

Nah you're right. Sorry for the over-the-top response. I'll dive more into the post referenced. It seems still a bit overkill for what I was after, just a basic almost C++ - like scope control over the default memberwise constructor

We also do have a public bug tracker, it's just a JIRA instance instead of github issues.

3 Likes

Btw you can generate the memberwise initializer via Xcode (right click on type > Refactor > Generate Memberwise Initializer) so you don’t have to write one out manually in this case. You can then tweak it as per your needs.

1 Like

That feature is grayed out for SwiftUI structs that have state or bindings

Terms of Service

Privacy Policy

Cookie Policy