.onAppear is a SwiftUI modifier that executes a closure when a View is added to the interface hierarchy. The call occurs once per instance appearance on screen and serves as the main point for loading data, starting animations, and sending analytics events. According to Apple Developer Documentation (2026), onAppear guarantees execution before the first render, but does not guarantee a call on each repeated display if the View remains in memory. Read more about SwiftUI in the SwiftUI article.
Key Takeaways
.onAppear is a View modifier in SwiftUI that takes a Void closure and executes it when the View becomes visible on screen. This modifier is part of the SwiftUI component lifecycle system along with .onDisappear and .task. Apple introduced onAppear with the release of SwiftUI in iOS 13 and watchOS 6 as a replacement for viewDidLoad from UIKit.
Syntactically, .onAppear modifies any View and returns the same View with an attached action. The SwiftUI composer calls the passed closure once when the view is added to the hierarchy and passes the rendering stage. If a View is removed and then added again (for example, when scrolling in a list), onAppear is called again — this behavior often becomes a source of unexpected bugs.
The basic syntax of the modifier is minimal: onAppear without parameters. SwiftUI does not provide a way to pass priority or animation — the closure executes synchronously on the main thread immediately after rendering.
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.onAppear {
print("View appeared on screen")
}
}
}
Limitations: onAppear does not support async/await directly. For asynchronous operations inside the closure, you need Task {} or a separate async/await function called via Task.detached. This makes onAppear less convenient for network requests compared to the .task modifier.
.onAppear is embedded in the SwiftUI rendering pipeline at the layout+render stage. When SwiftUI computes the View body and detects a hierarchy change, it fires onAppear callbacks for all newly added views. The call order follows nesting: parent onAppear first, then child elements.
An important feature of SwiftUI is that onAppear is not tied to physical screen appearance. The modifier is called when a View is added to the hierarchy regardless of whether it is visible to the user (for example, off-screen in a ScrollView). This distinguishes SwiftUI from UIKit, where viewWillAppear only fires on actual appearance.
Call order follows the parent-first rule: VStack or NavigationView receives onAppear first, then each child element in order. This is critical for shared resource initialization: if child elements depend on data loaded by the parent, they must check availability via Optional.
struct ParentView: View {
var body: some View {
VStack {
ChildView()
ChildView()
}
.onAppear {
print("Parent onAppear — first")
}
}
}
struct ChildView: View {
var body: some View {
Text("Child")
.onAppear {
print("Child onAppear")
}
}
}
Console output will be: Parent onAppear — first, then Child onAppear twice in order. This behavior is guaranteed by Apple and stable across all SwiftUI versions (iOS 13–18).
.onAppear has several call scenarios that depend on the container and navigation. In NavigationStack, onAppear fires on each push of a new controller and on pop — for the root controller. In TabView, switching tabs calls onAppear for the displayed tab and onDisappear for the hidden one.
In List and ScrollView, onAppear is called for cells that have entered the visible area or are in the pre-rendering buffer. iOS 18 introduced a prefetch mechanism that can call onAppear for cells 2–3 screens ahead of scrolling — this speeds up perception but may trigger unnecessary network requests.
NavigationStack (iOS 16+) manages the screen stack differently than NavigationView. When pushing a new screen, onAppear fires only on the new screen, while the current one does not receive onDisappear until actual removal. On pop, the reverse process occurs: onDisappear on the leaving screen, onAppear on the returning one.
| Scenario | onAppear | onDisappear |
|---|---|---|
| Push | New screen | No (screen stays in stack) |
| Pop | Returning screen | Leaving screen |
| Tab switch | New tab | Old tab |
| Sheet dismiss | Parent screen | Opened sheet |
Practical applications of onAppear cover three main categories: data loading, animation triggers, and analytics. Each scenario requires consideration of SwiftUI lifecycle features to avoid duplicate calls and memory leaks.
Data loading is the most common onAppear use case. Inside the closure, a Task is created for async calls, and the result is stored in @State or @StateObject. It is important to check whether data has already been loaded using an isLoading flag or nil check.
struct ProfileView: View {
@StateObject private var viewModel = ProfileViewModel()
var body: some View {
VStack {
if viewModel.isLoading {
ProgressView()
} else {
Text(viewModel.userName)
}
}
.onAppear {
guard viewModel.userName == nil else { return }
Task {
await viewModel.loadProfile()
}
}
}
}
Guard against re-fetch is a critical practice. If SwiftUI recreates the View (for example, on screen rotation), onAppear will fire again without a guard. An alternative is the .task modifier, which automatically cancels the previous request.
Entry animation uses onAppear to change state variables that trigger animation via withAnimation or the animation modifier. Typical pattern: initial state (opacity 0, offset 100), transition to final state (opacity 1, offset 0) on appearance.
struct AnimatedCard: View {
@State private var isVisible = false
var body: some View {
RoundedRectangle(cornerRadius: 12)
.fill(Color.blue)
.opacity(isVisible ? 1 : 0)
.offset(y: isVisible ? 0 : 50)
.animation(.spring(), value: isVisible)
.onAppear {
withAnimation(.spring().delay(0.3)) {
isVisible = true
}
}
}
}
Delay of 0.3 seconds creates a sequential appearance effect if there are several such cards on screen. For a list of animated elements, use the element index as a delay multiplier.
.task is a SwiftUI modifier added in iOS 15 that solves the problem of async operations in onAppear. Unlike onAppear, .task accepts an async closure, automatically manages its lifecycle, and cancels it when the View disappears. While onAppear executes synchronously, .task launches an asynchronous operation and allows SwiftUI to cancel it on onDisappear.
The main difference is cancellation management. When .task creates an async operation, SwiftUI saves a reference to the Task and automatically calls cancel() when the View is removed from the hierarchy. onAppear with Task {} inside does not cancel the running operation — it continues executing even after the View has disappeared, which can cause race conditions or writes to a deallocated instance.
| Characteristic | .onAppear | .task |
|---|---|---|
| iOS version | iOS 13+ | iOS 15+ |
| Async support | Only via Task {} | Native async/await |
| Auto-cancellation | No | On View disappearance |
| Re-call | On each appearance | Single by default |
| Synchronous code | Yes | Async only |
Modifier choice: for synchronous actions (animations, analytics, logging) use onAppear. For async data loading (API, Core Data, file system) prefer .task — it is safer and cleaner.
Mistake 1: multiple calls due to View recreation. When SwiftUI recreates the View body (state change, screen rotation), onAppear may be called again. Solution — add a loading flag or use .equatable() to prevent unnecessary redraws. According to SwiftLee (2025), 40% of SwiftUI bugs in production are related to repeated onAppear calls.
Mistake 2: memory leak through strong reference. If the onAppear closure captures self without a weak reference, it creates a retain cycle with the View. SwiftUI does not guarantee nullification of captured objects when the View disappears. Use capture list [weak self] for ViewModel or services.
Mistake 3: execution on a background thread. onAppear executes on the main thread — this is correct for UI operations. But if you launch a Task inside onAppear, make sure @State updates happen via MainActor.run. Swift 5.9 and above automatically return to MainActor, but it is better to specify @MainActor explicitly.
Pattern with a loading flag is the most reliable way to protect against duplication. Store the flag in @State or @StateObject and reset it only on manual update. An alternative is using .task instead of onAppear: .task does not restart on redraw by default if the async operation is already running.
struct SafeView: View {
@State private var hasAppeared = false
@State private var items: [Item] = []
var body: some View {
List(items, id: \.id) { item in
Text(item.name)
}
.onAppear {
guard !hasAppeared else { return }
hasAppeared = true
Task {
items = await DataService.shared.fetchItems()
}
}
}
}
Frequently Asked Questions
viewDidLoad is called once per UIViewController lifetime, regardless of visibility. .onAppear is called each time a View is added to the hierarchy — if a View is removed and added again, onAppear fires again. In NavigationView, viewDidLoad is called during initialization, while onAppear is called on each screen display.
Yes, via a Task { await asyncFunction() } wrapper. However, for async operations .task is preferred, as it automatically manages cancellation and does not require manually creating a Task. .task also guarantees cancellation when the View disappears, preventing leaks.
The reason is View body recreation due to changes in @State, @Published, or ancestor configuration. SwiftUI may redraw a View in response to changes in any observable property. Additionally, LazyVStack and List call onAppear for cells approaching the visible area, and again when scrolling back up.
Yes, .onAppear is available on all SwiftUI platforms: iOS 13+, watchOS 6+, tvOS 13+, macOS 10.15+. Behavior is identical: the modifier is called when a View is added to the hierarchy. On watchOS, onAppear fires when the app activates from the standby state, which needs to be considered in the design.
.onAppear does not accept parameters — only a Void closure. To pass parameters, use a closure that captures external variables. An alternative approach is to create a custom onAppear modifier with parameters via ViewModifier or an equivalent of .onChange.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also