The Simple Life(cycle) of a SwiftUI View in 2025
Revisiting an Elementary Topic... Half a Decade Later
When SwiftUI was first announced in 2019, we developers were trying to understand the new world from our old, UIKit one. And anyone who’s worked with UIKit knows that understanding the UI lifecycle is a crucial must.
That’s why one of my first articles was a comparison of what SwiftUI offered against the UIKit cycle we knew. An important quote I included in that article came from Paul Hudson who concluded that:
Your SwiftUI code will be maybe 10-20% of what your UIKit code was – almost all of it disappears because we no longer repeat ourselves, no longer need to handle so many lifecycle methods, and more. - Paul Hudson, Hacking With Swift
To this day, I still receive comments and private messages citing that article by those just entering (or still trying to figure out) SwiftUI.
Almost 6 years later, I’m here to say that the SwiftUI Lifecycle is worth revisiting, expanding upon, and without focusing on comparisons. The reasons being:
Apple has since defined an entire App Lifecycle around SwiftUI
The Lifecycle of a View has gained capabilities
UIKit may still be relevant to your needs, but Apple is no longer pushing it as the gold standard. As time goes on, UIKit will most likely become the exception/edge case in modern repos
Bold statements, but sometimes we need to read the wind and sea the best we can as we discern how best to approach the future of development.
The View Lifecycle
So, without further ado, let’s start with the beginning (that’s a very good place to start, after all).
init
The first stop is the initializer of a View. This is our chance to setup all our properties (run their respective initializers, as well), and do any other light-weight operations before we start rendering our View.
We get init because View is a struct. It is true that a struct gets memberwise initializers for free (initializers generated based on the defined properties of a struct). But if there’s anything else that needs to happen besides simply passing values into their respective properties, then this is the space to do so.
As an aside, if you would like to learn more about SwiftUI initializers, check out my article init() to Win It.
task
With the introduction of the task modifier, we’re given a closure that’s triggered after initialization (so any non-optional properties should be available to us) but before the view appears.
What’s special about task is that it’s an asynchronous closure, so we can assign a task priority and run async code (or not). It’s also tied to the lifecycle of the View. This means that if a task is started, but the View is going away, it will automatically cancel the task for you.
I’ve found this is a great starting place to trigger fetches asynchronously and safely.
An interesting note to make here is Apple’s recommendation to use the task modifier to initialize intensive Observable objects.
onAppear
As the name suggests, this triggers a closure once the View has finally appeared. Note that this is not asynchronous.
This is particular useful for operations requiring that the View already be presented, especially if we need to rely on any calculations to be done in terms of UI elements, themselves. For an example, scrolling to a certain position in a ScrollView or List.
States and Data Flows
Once initialized, a SwiftUI View lives and breathes through its state and data flow. SwiftUI relies on a reactive data model to determine when a view updates.
@State: Local state, private to the view. This can hold an immeadiate, basic object (String, Int, etc.) or, with the advent of Observation, an Observable.
@Binding: A reference to state owned by a parent.
@StateObject & @ObservedObject: A reference to an external ObservableObject (Combine) with data updates.
@Environment/@EnvironmentObject: A globally injected Observable/ObservableObject (respectfully), often for app- or experience-wide state.
SwiftUI automatically re-evaluates the body when any state it depends on changes. This is a core difference from UIKit, where you must manually trigger UI updates.
Having said that, managing state effectively is crucial because it defines how long a view "lives" and when it updates. (To learn more on how to accomplish this, check out my article on state management)
onDisappear
The onDisappear modifier is the last opportunity for cleanup before a view is removed from the hierarchy. This is useful for:
Stopping ongoing tasks manually (e.g., network requests, timers)
Persisting data before the view vanishes
Sending analytics or logging events
However, be aware that onDisappear doesn’t always mean the view is deallocated—SwiftUI may reuse views in certain cases. If true deallocation behavior is needed, then as Yoda said: “there is another”.
deinit (a sneaky Observable bonus)
This one is extremely arguable, but worth mentioning. Because Views are a struct, they don’t have deinitializers. If, somehow, there was something you wanted to trigger just before onDisappear and your View uses an Observable state, then you could place it in the deinit of the Observable.
This wouldn’t necessarily be owned by the View, so it doesn’t truly count. But let’s say you wanted to send one last broadcast or task out and it involved a value from that Observable. The Observable deinit occurs before the Views onDisappear, so it would need to be triggered from there or else the value will no longer be available.
And, as mentioned in the last section, onDisappear does not mean the View is deallocated.
Back in the init section, I briefly mention how a View’s init may trigger other initializers. Again, those aren’t specifically part of the View’s lifecycle, but relying on Observables based on class provides this interesting tool in the tool bag that you may never really need… until you really do.
In Memoriam
So there we have it, our View Lifecycle in a nutshell. There’s absolutely more to it than this, and there are resources available that get into the nitty gritty. But knowing these “eras” of the lifecycle at an almost categorical-level will help your SwiftUI code thrive, both in life and deallocation.
The task explanation could be misconstrued. Like onAppear, the task modifier's closure is triggered just before the view is scheduled to appear on the screen.
It's not tied to view *initialization* at all (other than the fact that initialization must happen first). In fact, view initialization could occur many, many times before task ever happens at all.