How to correctly initialize a view model in SwiftUI: https://chris.eidhof.nl/post/swiftui-view-model/ (for when the view needs ownership of the view model).
SwiftUI View Model Ownership — Chris Eidhof

@chris I agree this is a tricky subject. Nice article! Ping @jesly
@chris Thanks for the clarification! Do you know what the downsides of using the State(initialValue: ...) initializer are? I have a feeling that it's not a good way to init the view model as well, but I don’t really know why.
@benboecker it's just setting the initial value. Do you have some more context for that question?
@chris @benboecker How about a view model that requires a dependency, like a tint (user’s choice) or perhaps another view model? I’ve been initializing since forever with State(wrappedValue:). Is this the wrong way to do that? Maybe use initialValue? A priori I’m getting the behavior I need and instruments doesn’t reveal leaks or over allocations. 🤔
@chris thank you for writing these articles. They are a fantastic resource.
@chris In the fixed example, is it correct to say that an instance of the view model object will be needlessly created each time the view’s initializer is called by the parent? As in: when you change the selected room in the parent, the child’s initializer is run, name is set to the new name, the view model object is created and then not used (because of the attribute graph as you mentioned), and then the “original” view model is replaced by a third inside of onChanged?
@kyle @chris yes, that is correct
@shadowfacts @kyle yes! Good catch. I think that should be fixed, don't know the proper solution yet... I'll have a think.
@shadowfacts @kyle I added a note to the post just now (should be live in a minute). Thank you!

@chris @shadowfacts @kyle echoing the above, I think this is made more explicit if you use the State init, like _vm = State(initialValue: …)

This makes it more obvious why the initial example was broken and avoids a new vm on every init (but you still need your onchange)

@bens @chris @kyle unless I'm misunderstanding, that does not avoid the new view model on every init. State(initialValue:) would have to take an autoclosure for that to be the case

https://developer.apple.com/documentation/swiftui/state/init(initialvalue:)
init(initialValue:) | Apple Developer Documentation

Creates a state property that stores an initial value.

Apple Developer Documentation
@shadowfacts @chris @kyle StateObject did, I assumed this did as well
@bens @chris @kyle indeed not. there was a very brief window during one of the betas (after observable was introduced, iirc) where there was an autoclosure State init, but it disappeared

@shadowfacts @chris @kyle woof -- here's to hoping there's some magical new tool that eliminates all this confusion.

IME it’s far too easy to just not realize you're accidentally recreating state 60 times/sec because you modeled it incorrectly.

@chris @shadowfacts You’re doing yeoman’s work, thank you. My ulterior motive for confirming that detail is I that I am coming to the conclusion—and want to learn how to communicate—that this pattern of view models are implicitly discouraged by Apple and SwiftUI, based on the worsening ergonomics of using them.
@kyle @chris I largely agree and don't really use view models myself, but I think it's fine (maybe not great, but fine) to do so without worrying about the extra initializations unless you have concrete/measurable reasons to

(I have written about related things before: https://shadowfacts.net/2024/swiftui-lifecycle/)
Your View's Lifetime Is Not Yours

The outer part of a shadow is called the penumbra.

Shadowfacts

@shadowfacts @kyle I updated the article to show a variant that doesn't always recreate the VM. It's tricky.

BTW, @shadowfacts I really enjoyed reading your article about preferences!

@kyle @shadowfacts yeah I don't hold strong opinions there, but people *really* want to initialize in init. I wrote the post more so that when I see that happening, I have a "short" explanation that is linkable :).

@chris thanks for the article!

How should one go about creating StateObjects in view’s init? Should one avoid init(wrappedValue:) ?

@chris ah thanks for this. we go back and forth on how we should be managing this on our team a lot. an article to point to from a member of the community like you is super helpful, fwiw.

lots of follow up questions i could ask, but i’ll limit to just this one for now: are there effects to making bare view properties like `name` vars instead of lets?

i’ve always made those lets, but i don’t really have a good reason to i guess, and now i’m wondering if i’m doing something bad after seeing you use vars.

@rituals it doesn't matter whether they're var or let, the structs are recreated from scratch every time anyway.

@chris The way I've been handling it (in small hobby apps, take with grain of salt) is to *always* put view models in the environment. In line with your article's advice, this has the advantage that it ensures state is updated in a body function.

Relevant to a discussion I was having with @orj recently, SwiftUI thrives on very few view models so using the environment makes sense (better drives a deep tree of views or passing through utility layout views).

@cocoawithlove @orj yeah, tbh, I don't use view models. The reason I wrote is is that this specific question in this form (the view should own it) comes up so often that I figured it'd be nice to have something to link to.
@chris What approach do you use instead of view models, typically?
@thehonkingsmith mostly plain structs in regular or state properties. I don't really build real apps 😅
@cocoawithlove @chris @orj Interesting approach. How would that work with multiple rooms in the same view hierarchy? Like in a list?
@mamouneyya @cocoawithlove @orj I think it'd work well?
@chris @cocoawithlove @orj Well, the type is used as a key which means you cannot have more than one in the same hierarchy, right? I guess that’s fine if the parent of that view passed the view model only for that subtree, but then I question the point of using the Environment at all.

@mamouneyya @chris @orj Environment values are not globals. The environment is a scoped context – so it exists only in the subtree where you apply it.

This means you can apply a separate environment value for every row in a list, and they will not interfere with each other.

@cocoawithlove @chris @orj Yeah, I know, but that means that you have to pass the single view model in the subtree of each cell, which makes me not see the value of using the Environment in this case (say, as opposed to passing the view model directly to the constructor as an @ ObservedObject). Or maybe it makes the “ownership” easier as you don’t have to worry about it? Usually when I think about the Environment, I am always too focused on the “propagation” aspect.
@mamouneyya @cocoawithlove @orj I’m not sure either. I guess one reason is if a bunch of subviews need that same VM?
@chris I have a related question since we’re talking about owning the view model. I am trying to design a self-contained component that has its own private @‎ StateObject. The problem is that when it’s added to a list, it loses its state every time it goes offscreen while scrolling. I know the view gets destroyed, but shouldn’t the state be preserved by SwiftUI? If not, what’s the point of owning the state, because that would never work in a list scenario?
@mamouneyya yeah maybe this is what you're seeing: https://chris.eidhof.nl/post/swiftui-state-lifetime/ (article is from 2022, might have changed).
Lifetime of State Properties in SwiftUI — Chris Eidhof

@chris Mmm, that was not my observation. I can share sample code tomorrow.

@chris Thanks for the article Chris. You clearly lay out the pitfalls here in some common patterns and how SwiftUI is working.

However I’d argue that issue being showcased by the chosen example indicates that the source of truth is being created / held at the wrong level.

@adamc yeah! But somehow people still really want this. I think once they see the effort involved they might be open to changing their minds?
@chris how about holding the viewModel in a plain `let` rather than an `@State`?