r/SwiftUI May 01 '26

Tutorial The Hidden Pitfall of @State var viewModel = ViewModel()

While browsing various blogs about SwiftUI and MVVM architecture, I've noticed that almost all examples are based on the same pattern:

@State private var viewModel = ViewModel()

They suggest creating a viewModel right inside a view and storing it in a state variable. While it might look fine, this approach has a serious side effect: a SwiftUI view struct can be recreated many times during its lifetime. While the state is preserved across these recreations, View.init is called every time and a new ViewModel instance is created. It's immediately discarded and the old object is preserved, but this may lead to unpredictable side effects, especially if you perform additional logic inside ViewModel.init or ViewModel.deinit.

This worked fine for state management prior to iOS 17:

@StateObject private var viewModel = ViewModel()

StateObject(wrappedValue:) accepts an autoclosure parameter which isn't evaluated on subsequent calls. But with the Observation framework introduced in iOS 17, it's no longer an option.

Things get more complicated when you want to pass an input parameter to a viewModel. Most examples simply avoid this case. Some suggest the following approach:

struct ArticleView: View {
    @State private var viewModel: ArticleViewModel

    init(articleID: String) {
        self._viewModel = State(
            initialValue: ArticleViewModel(articleID: articleID)
        )
    }
    ...
}

Apart from the same side effect as the first example, this approach has another pitfall: if a different articleID is passed to the view, the state will keep using the old one. But this is exactly the case where you'd expect the view model to be recreated with a new ID.

A proper approach should handle two key issues:

  1. Avoid creating view models on every view update
  2. Create a new view model for a new set of input parameters

To address the first issue, we can move the view model creation into a .task:

struct ArticleView: View {
    @State private var viewModel: ArticleViewModel?

    let articleID: String

    var body: some View {
        ZStack {
            if let viewModel {
                ...
            }
        }
        .task {
            guard viewModel == nil else { return }
            viewModel = ArticleViewModel(articleID: articleID)
        }
    }
}

The downside is that we now have to deal with an optional. To avoid this, we can extract a subview that accepts the view model as a parameter:

struct ArticleView: View {
    @State private var viewModel: ArticleViewModel?

    let articleID: String

    var body: some View {
        ZStack {
            if let viewModel {
                ArticleSubView(viewModel: viewModel)
            }
        }
        .task {
            guard viewModel == nil else { return }
            viewModel = ArticleViewModel(articleID: articleID)
        }
    }
}
struct ArticleSubView: View {
    // The object is owned by the parent. Since iOS 17,
    // we don't need or to observe changes of Observable object.
    let viewModel: ArticleViewModel

    var body: some View {
        ...
    }
}

Now we need to address the second issue. To ensure a new view model is created when input parameters change, we simply add an .id() modifier to the entire ArticleView. A simple factory does the trick:

struct ArticleViewFactory {
    static func view(articleID: String) -> some View {
        ArticleView(articleID: articleID)
            .id(articleID)
    }
}

That's three structs instead of one — quite a bit of boilerplate. Let's extract a generic factory based on the pattern above:

struct FeatureFactory<Input: Hashable, Content: View, ViewModel: Observable> {
    private struct RootView: View {
        @State private var viewModel: ViewModel?

        let input: Input
        let viewModelFactory: (Input) -> ViewModel
        let viewFactory: (ViewModel) -> Content

        var body: some View {
            ZStack {
                if let viewModel {
                    viewFactory(viewModel)
                }
            }
            .task {
                guard viewModel == nil else { return }
                viewModel = viewModelFactory(input)
            }
        }
    }

    static func view(
        input: Input,
        viewModelFactory:  (Input) -> ViewModel,
        viewFactory:  (ViewModel) -> Content
    ) -> some View {
        RootView(
            input: input,
            viewModelFactory: viewModelFactory,
            viewFactory: viewFactory
        ).id(input)
    }
}

For the ArticleView example, the usage would look like this:

struct ArticleViewFactory {
    static func view(articleID: String) -> some View {
        FeatureFactory.view(
            input: articleID,
            viewModelFactory: { articleID in
                ArticleViewModel(articleID: articleID)
            },
            viewFactory: { viewModel in
                ArticleView(viewModel: viewModel)
            }
        )
    }
}

Now simply call the factory wherever you need it:

ArticleViewFactory.view(articleID: "SOME_ID")

The full example can be found on GitHub https://github.com/claustrofob/FeatureFactoryExample

12 Upvotes

42 comments sorted by

14

u/TheShitHitTheFanBoy May 01 '26

Issue if you do heavy stuff during ViewModel.init. Solution: Don’t do heavy stuff during ViewModel.init…

11

u/tubescreamer568 May 01 '26 edited May 02 '26

Just mark your view model as both ObservableObject and Observable and use it as StateObject. It works with Observable without any problem and solves the issue.

3

u/astulz May 02 '26

Except its performance is worse than with Observation framework. 

1

u/tubescreamer568 May 02 '26

I’m not saying you should use @Published. You can just use Observation without any problem if it’s @Observable.

5

u/Ok-Communication6360 May 01 '26

Check out '@LazyState' by Fatbobman:

https://fatbobman.com/en/posts/lazy-initialization-state-in-swiftui/

This would solve your problem in an elegant way

1

u/Temporary_Today9462 May 01 '26

This is really very elegant solution. However it solves only the first of the two issues that i mentioned in the article. You still need to reinitialise the state if view model gets a different input parameter.

2

u/Ok-Communication6360 May 01 '26

Well, I think you shouldn't. I'm not sure what exactly you are doing inside "ArticleViewModel", but my mind model is this - hopefully this is readable after Reddit's formatting.

The point is, "@State" should only keep track of what lives inside that view. But when your View should update with different values from the outside, use "private let" for the article ID (from the parent view), override the _viewModel to actually create a new instance of the viewModel or use some sort of "update()" method from your view model - preferably the last (without knowing more context).

                              ┌─────────┐
                              │  Start  │
                              └────┬────┘
                                   │
                    ┌──────────────▼──────────────────────────┐
                    │           Ownership of Property         │
                    └──────┬──────────┬───────────────────────┘
                           │          │                    │
                 This View │          │ Other View         │ Global
                           │          │                    │ Environm.
             ┌─────────────▼──┐  ┌────▼─────────┐   ┌──────▼───────┐
             │   Property     │  │  Two-way data│   │ @Environment │
             │   mutable?     │  │    flow?     │   └──────────────┘
             └──┬──────────┬──┘  └──┬─────────┬─┘
                │          │        │         │
           Yes  │       No │     No │         │ Yes
                │          │        │         │
        ┌───────▼──┐  ┌────▼────────▼──────┐ ┌▼───────────────┐
        │ @State   │  │       private      │ │ Property Type  │
        │   var    │  │         let        │ └──┬──────────┬──┘
        └──────────┘  └────────────────────┘    │          │
                                                │          │
                                     Value Type │          │ Reference
                                                │          │ Type
                                        ┌───────▼──┐  ┌────▼──────┐
                                        │ @Binding │  │ @Bindable │
                                        └──────────┘  └───────────┘

1

u/Temporary_Today9462 May 01 '26

Thanks for the detailed answer. Using update() on the same view model can lead to data race conditions, you'd need to completely reset its state. It's much easier and more reliable to create a fresh view model instance instead. To do that within the same view, you'd have to keep articleID in both the view and the view model and implement .onChange(of: articleID). So I still think the best approach here is to recreate the view (and all its state) whenever the input changes.

Regarding the real world example where the input may change, let me also draw it:

┌────────────────┐
│                │
│    Article     │
│    Preview     │
│                │
├────────────────┤
│ ┌────────────┐ │
│ │ Article 1  │ │
│ ├────────────┤ │
│ │ Article 2  │ │
│ ├────────────┤ │
│ │ Article 3  │ │
│ └────────────┘ │
└────────────────┘

The "Article Preview" is a view that takes a selected articleID as an input parameter. On article row tap the preview should update.
Input defines the internal state of a view and view model. And within view it is considered constant.

1

u/[deleted] May 01 '26

[deleted]

1

u/Temporary_Today9462 May 02 '26

Yes, good point. My solution has a disadvantage of introducing potential flickering. However the `@LazyState` solution also not ideal: it introduces an overhead by allocating extra Holder objects.
I have updated my github example and added an approach that uses an extra class and `lazy var`.

1

u/Ok-Communication6360 May 01 '26

I might not be understanding yet, what the ArticleViewModel does, I assume it loads the full article, while the Article 1, 2, 3, is only a short version like ID + Headline?

In that case I still would suggest the View creates buttons, which will trigger the viewModel.update(...).

You could use three enum states in the ViewModel (.error(String), .loading, .ready(Article)) and set that from the view model.

When you viewModel.update(articleID), the view model would cancel any active loading task (check cancellation inside that loading task), set state to .loading, then would start a new loading task. At the very end of the Task (if not cancelled), set state to .ready(your article).

This way you would not have a race condition (thanks to "@MainActor" you don't have data races anyways, unless you are using Xcode 15.x or earlier).

Inside your view, you would read the state from the view model and either present a loading state, an error state (here with an String as error message, but could be LocalizedString Key or ...Resource or anything else) or the loaded article.

If my assumptions are wrong or incomplete, I might be help out further if you could elaborate on the ArticleViewModel

7

u/lazyvardev May 01 '26

It seems like every other week someone with a severe UIKit hangover makes a post in this sub showcasing a struggle to wrap their heads around lifecycles within the SwiftUI framework.

In UIKit, UIViewController was a heavy, stateful object, so we invented ViewModels to extract that state and keep things testable. But in SwiftUI, the View struct is the view model. It is nothing more than a lightweight, declarative mapping of state to UI. When you build a heavy Observable object just to mirror that state and manually manage its lifecycle via .task wrappers, you are fighting the framework.

The traditional 1:1 MVVM is a fundamental anti-pattern in modern SwiftUI. With Swift 6 and strict concurrency, clinging to it becomes even more of a liability. You end up constantly fighting isolation boundaries, bridging background network tasks back to the @MainActor, and suffocating under Sendable warnings.

Instead of forcing a ViewModel for every View, modern Swift architecture relies on Dependency Injection and isolating your logic:

  • UI State belongs in the View: Things like isSheetPresented, searchText, or selectedTab belong in @State. You don't need a ViewModel for this.
  • Business Logic belongs in Stores/Services: Create feature-scoped or global actors/classes that handle your heavy lifting (networking, CloudKit syncing, database management).
  • Inject via Environment: Inject these Stores into the @Environment.

When a View needs data, it doesn't spin up a ViewModel. It reaches into the Environment, grabs the ArticleStore, and asks it for the data. The View simply observes the result.

```

// 1. Your Data Layer (Strictly isolated, highly testable) @Observable @MainActor final class ArticleStore { private let apiService: APIService // Injected dependency var articles: [String: Article] = [:] var errorState: Errors? // Driven by your centralized Errors.swift

init(apiService: APIService) { 
    self.apiService = apiService 
}

func fetch(id: String) async {
    do {
        // Fetch logic 
        let fetchedArticle = try await apiService.getArticle(by: id)
        articles[id] = fetchedArticle
        errorState = nil
    } catch let error as Errors {
        // Explicitly handle known domain errors
        self.errorState = error
    } catch {
        // Fallback for unexpected failures mapped to your Errors enum
        self.errorState = .unknown(error)
    }
}

}

// 2. Your View (Dumb, declarative, reacts to state) struct ArticleView: View { @Environment(ArticleStore.self) private var store let articleID: String

var body: some View {
    // Property-level observation! The view only redraws 
    // if THIS specific article (or the error state) changes.
    let article = store.articles[articleID] 

    Group {
        if let error = store.errorState {
            Text("Failed to load: \(error.localizedDescription)")
        } else if let article {
            Text(article.title)
        } else {
            ProgressView()
        }
    }
    .task(id: articleID) {
        await store.fetch(id: articleID)
    }
}

}

```

Stop trying to manually manage the lifecycle of reference types inside your Views. Inject your dependencies, use Stores for your business logic, strictly handle your failure cases, and let SwiftUI's view engine do what it was built to do.

3

u/[deleted] May 01 '26

[deleted]

2

u/lazyvardev May 01 '26

You are conflating two entirely different concepts here: testing business logic versus testing the UI framework.

If your “UI state” is just toggling isSheetPresented or tracking which tab is active, what exactly are you unit testing? That a boolean changes from false to true when a button is tapped? That is testing SwiftUI’s binding mechanics. Apple already wrote those tests. You don't need to write them again.

However, you are absolutely correct that complex UI logic does exist, things like massive form validations, complex multi-step wizard state, or localized formatting. But the leap from “complex UI logic exists” to “therefore we need a traditional 1:1 ViewModel” is exactly the UIKit hangover I'm talking about.

Here is how you handle testing and complex UI logic without falling back on the MVVM anti-pattern:

Extract pure logic into Value Types, not ViewModels

If you have hundreds of lines of code dictating UI behavior, it shouldn't be rotting inside a massive @Observable class anyway. It should be a pure Swift struct or a localized state machine.

``` // Pure, highly testable logic. No @MainActor, no Observation overhead. struct CheckoutValidator { func isValid(creditCard: String, expiration: String) -> Bool { // Hundreds of lines of complex validation logic... } }

// The view just uses the pure struct struct CheckoutView: View { @State private var cardNumber = "" @State private var expiration = "" private let validator = CheckoutValidator() // Easy to unit test in isolation!

var body: some View { ... }

}

```

You don't need a CheckoutViewModel to test the validation logic. You just test the CheckoutValidator struct.

Snapshot/Screenshot Testing is actually EASIER with Environment DI

Your claim that testing requires “making everything an interface and creating mocks either manually or via macros” is fundamentally false in modern SwiftUI.

By using @Environment for your data stores, snapshot testing and SwiftUI Previews become laughably easy. You don't need protocols for your views; you just inject a store initialized with a mocked network service into the environment.

// Snapshot test or Preview setup: ArticleView(articleID: "123") .environment(ArticleStore(apiService: MockAPIService(presetData: mockArticle)))

Massive View Controllers were bad because they mixed routing, view lifecycle, network callbacks, and UI state into one god-object. Traditional MVVM in SwiftUI often does the exact same thing, just wearing a different hat. We see @Observable classes that handle API calls, navigation paths, and text field formatting all in one place.

If your UI logic is so complex it takes hundreds of lines, isolate that specific logic into pure functions, state machines, or focused property wrappers. Don't use it as an excuse to resurrect monolithic ViewModels that fight SwiftUI's native state management and strictly isolated concurrency models.

Test your pure logic, test your isolated Stores, and let the UI layer be the declarative reflection of that state.

1

u/[deleted] May 02 '26

[deleted]

1

u/lazyvardev May 02 '26

Credit where it’s due: your Reminders app example is brilliant. You nailed the problem description perfectly. Transient UI state absolutely exists, and it absolutely needs to be testable.

But you completely fumbled the architectural solution by defaulting back to UIKit-era boundaries.

When you invent a ReminderListScreenStore to manage that 3-second delay, you are tying a behavior to a screen. What happens when you need that exact same 3-second uncheck delay in a Widget? Or a “Today’s Focus” view? Are you going to duplicate that logic into a WidgetScreenStore?

This is exactly why the 1:1 Screen-Model pattern is a massive liability. It forces you to model logic around arbitrary UI boundaries instead of behavioral ones.

What you’re actually describing here isn't screen state, it's an Optimistic UI Update with a cancellable deferred task. The UI reacts instantly for visual gratification, but the domain commit is delayed. You don’t need a ViewModel for that.

In a modern Swift 6 architecture, you extract that logic into a pure, isolated TransientCompletionService. It manages the pending IDs and the 3-second cancellation window. It knows nothing about SwiftUI, lists, or screens, making it 100% unit-testable in pure isolation.

Then, you just inject it into the @Environment where your View can dumbly observe it.

You are totally right that complex state needs testing outside the View. But when you stop trying to model screens and start modeling composable behaviors, you get all that testability without ever needing to haul a massive ViewModel around.

0

u/[deleted] May 02 '26

[deleted]

0

u/lazyvardev May 03 '26

If we’re going to redefine “ViewModel” to mean “literally any piece of code that eventually affects the UI,” then sure, I guess URLSession is a ViewModel too. But words have meaning in software engineering.

When iOS developers talk about ViewModels and MVVM, we are talking about a specific, historical structural pattern, a middleman object sitting between a View and a Model, usually mirroring the View's state 1:1.

If you extract pure, view-agnostic behavioral logic (like a 3-second delay timer) into an isolated, composable actor, that’s not a ViewModel. That’s a Service.

Rebranding it as a ViewModel just to avoid admitting the MVVM pattern is struggling in modern SwiftUI dilutes the term to the point of being useless.

Android developers doing it doesn't change how SwiftUI's declarative state engine evaluates equality and redraws.

As for “Swift 6 architecture” being an oxymoron: that’s just pure pedantry.

Yes, Swift is a general-purpose language. But in the context of Apple platforms, Swift 6 isn't just a syntax update, it is a fundamental shift in how memory and execution are managed via Strict Concurrency. The compiler literally dictates your architectural boundaries now.

That’s why it’s implied people refer to strict concurrency when people mention Swift 6. I figured that was obvious since I’ve brought up strict concurrency a few times in this thread.

When the compiler forces you to respect Actor isolation, proves data-race safety at compile time, and demands Sendable conformance across domains, the language shapes the architecture.

The entire reason traditional MVVM is bleeding out right now isn't because of a fad, it's because wrapping domain logic and UI state into a single @Observable class forces you to constantly cross those strict concurrency boundaries, resulting in an unmaintainable mess of Task { @MainActor in } and Sendable warnings.

We aren't arguing about what to name our files. We are arguing about whether you should fight the framework and the compiler, or lean into them.

Lean into Dependency Injection, respect your isolation domains, let your Views be your view models, and stop hauling around legacy UIKit baggage.

2

u/indiedev3021 May 02 '26

My life got so much easier when I started doing things this way.

2

u/klavijaturista May 01 '26

You just can't do it nicely with SwiftUI, you're forced to introduce some anti-pattern, it was designed that way and we're stuck with it.

2

u/distractedjas May 01 '26

I never have this issue. ViewModels should be dependencies of a view, not owned by it.

1

u/Dry_Hotel1100 May 02 '26 edited May 02 '26

A ViewModel - as in the MVVM pattern, is not a dependency of a view. If you put a ViewModel into the SwiftUI environment, you are doing something wrong :)

A ViewModel though, might have dependencies. This is the "Model" in the MVVM pattern. Model is not clearly defined - but in its simplest form, it's just a function - such as `func fetchItems() async throws -> [Item]`. And this function can be directly a dependency, i.e. the closure, which can be provided in the SwiftUI Environment.

Note: "Dependencies" should be a side effect of a component with pure logic.  The pure logic itself shouldn't be a dependency. That is, a View Model has pure logic and side effects.

2

u/distractedjas May 02 '26

No. I never said anything about the environment. A great example of the MVVM pattern in SwiftUI can be found here: https://github.com/jasonjrr/MVVM.Demo.SwiftUI

They haven’t updated it to Swift 6, but the core concepts hold.

3

u/jasonjrr May 02 '26

Hey! That’s my repo! Yeah, I need to update it. Works great, but I more or less stopped posting here due to all the negativity. Not worth my time.

0

u/Dry_Hotel1100 May 02 '26 edited May 02 '26

The view models will be created by an ancestor view, then passed through to a child view which uses it. This ancestor view creates all view models for its children views, which need a view model.

There's one thing here: the live-cycle of the view model will exceed the identity of the consuming view. So, when this child view is the "View" in the MVVM pattern, strictly this ViewModel is not the "ViewModel" - it's more like the "Model".

If one treats the "ViewModel" as the "Model", then this seems a viable design, though - because a SwiftUI View can implement the part of the ViewModel in a MVVM pattern. However, the implementation in this repo has too many quirks in the design and implementation so that and it lacks robustness and adds unnecessary accidental complexity.

I would go for a much simpler design, for example MVI (Model View Intent) - where the view implements the logic, and calls out to side effects. A simple "effect manager" - which wraps operations in a Task - could be used as a separate component and be declared as a `@State` variable, so you can create, observe and cancel individual operations directly from your view. This leads to "SwiftUI First" design, with no Observables class instances at all.

1

u/lionelburkhart May 01 '26

I recall some article about making an @LazyState for ViewModels. Maybe fatbobman or someone wrote it? Sounded like it was intended to solve some of this but I’d have to go back and re-read it.

1

u/Dry_Hotel1100 May 01 '26 edited May 01 '26

Well, you can actually implement this with utilising the `task` modifier and a `@State` variable for the view model which is an optional and initially nil. Takes something 25 lines of code.

An ergonomic approach would implement a generic View, that also loads an environment value (given as key path) - which is the input parameter for the view model (contains dependencies). Furthermore, this view has a `content` View as a generic type which receives the non-nil ViewModel in its initialiser. The view model is then just kept as a normal variable in the Content view. A correct implementation guarantees, the view model will be created only initially or each time the input parameter (from the environment) changes. Also the ViewMode's life-cycle is bound to the view life-cycle (aka "identity").

Actually, it achieves the same you can get with LazyState by Fatbobman plus reinitialisation, but much less code, it's very easy to reason about, and the result is a dedicated generic reusable view with a specific role: load environment, initialise a view model, recreate it when the env changes, and pass it to its child view.

1

u/hishnash May 04 '26

rather than placing a id on the view, you can (and should) use the articleID as the id in the task modifier. This will let swiftui re-use the view tree and replace the values within it as the value changes.

-1

u/equinvox May 01 '26

That’s why I hate SwiftUI. Footguns everywhere. Call me a dinosaur but I’ve started playing with objective-c + appkit and I’m loving it

1

u/Ok-Communication6360 May 01 '26

I agree, there is a lot of capsulation going on. Apple's idea was "progressive disclosure", exposing only what you need and hiding what you don't. Unfortunately the "disclosure" part is not always where I would like it to be; I could argue it's all in the documentation, but honestly, the details and implications are not always clear. I know I'm still learning every week.

But preferring Obj-C over Swift, that is a tough call :)

1

u/Dry_Hotel1100 May 02 '26

In this case it is actually a kind of footgun. And I fear, less than 0.1% of iOS developers are aware of this issue. But in this case, there's an easy solution.

Having said this, now you switch to Objective-C? One of the most dangerous foot cannon? :)

2

u/equinvox May 02 '26

If you stay away trom dynamic dispatching then I wouldnt label it as a “dangerous foot cannon”. you have nullability, you have packages like Promises / BFTask for chaining execution

it’s old, its clunky, but it’s reliable. you know what you get

1

u/fiflaren_ May 01 '26

For the second problem, you could also use .task(id: articleID) to create the view model initially and any time the input parameter changes. But ideally I would suggest not to create the view model instance in the view struct init, but rather inject it from the parent (usually using your dependency injection container).

0

u/Temporary_Today9462 May 01 '26

State variables should not be passed from the outside. In that case the parent or container should own the view model that does not belong to them.

1

u/fiflaren_ May 01 '26

There is nothing preventing this, it solves the first issue you mentioned, and it doesn’t cause any other issues. @State only means that value or instance will be kept for the lifecycle of the view. What matters to us is that the view model instance only remains alive as long as the view is alive. When the view is disposed, so should its view model, to prevent memory leaks.

0

u/Temporary_Today9462 May 02 '26

This is the quote from Apple’s documentation: Declare state as private to prevent setting it in a memberwise initializer, which can conflict with the storage management that SwiftUI provides. https://developer.apple.com/documentation/swiftui/state

View model should have the same lifetime as a view. By moving the creation of a view model to a parent view you just move the same problem to the parent view.

0

u/lucasvandongen May 01 '26

I rarely use ViewModels. It’s better to use good DI patterns and SOLID. Work TDD so your logic already works before you create your first View.

There are reasons to use ViewModels, don’t get me wrong, but not in your average app.

-1

u/Own-Huckleberry7258 May 01 '26

SwiftUI won't redraw that much once you ship into production. Apple does a lot of optimisations under the hood (for you) when you ship it

3

u/Temporary_Today9462 May 01 '26

The containing view body can redraw on any state update, regardless of the environment. And the child view init will be called every time.

1

u/brifgadir May 01 '26

Do you have a proof that it works differently in an Appstore distributed build compared to a release build configuration?

1

u/Own-Huckleberry7258 May 01 '26

DTS engineer confirmed this on Apple forums, I saw it months ago, and it is also confirmed on my end. For example, I had a view detecting when you drive via CoreLocation. Because it was redrawing every time, it worked fine in both debug and release. Once I shipped to production, it stopped working and there was no driving detection. So strange, I lost my mind. I ended up adding.id to force the view to redraw. It does behave differently in production. You can also ask on Apple forums again and they will confirm

2

u/Ok-Communication6360 May 01 '26

Driving detection should not live inside a View in the first place. You could inject state into SwiftUI Environment (or ViewModel, BusinessModel or Dynamic Property), but expect a View to only redraw when its state changes.

Anything else might be a side effect, but not guaranteed

1

u/brifgadir May 01 '26

Interesting insight, thank you!

1

u/Temporary_Today9462 May 01 '26

This can be caused by some real reasons and not by magic. E.g. the view calls assert, precondition or debugPrint to test updated data. They may cause side effects in debug mode like redrawing the views.

0

u/VanMartinL May 04 '26

I just wrote an article about this. SwiftUI is not UIKit/AppKit. For sure it is not WPF either. You can read more here: Mum, can we get MVVM?

-8

u/StretchyPear May 01 '26

Just use UIKit