.onAppear는 View가 인터페이스 계층 구조에 추가될 때 클로저를 실행하는 SwiftUI 수식자입니다. 호출은 화면에 인스턴스가 나타날 때 한 번 발생하며 데이터 로드, 애니메이션 시작 및 분석 이벤트 전송을 위한 주요 지점 역할을 합니다. Apple Developer Documentation(2026)에 따르면 onAppear는 첫 번째 렌더링 전에 실행을 보장하지만 View가 메모리에 남아 있는 경우 반복 표시될 때마다 호출을 보장하지는 않습니다. SwiftUI에 대한 자세한 내용은 SwiftUI 문서를 참조하세요.
핵심 요약
.onAppear는 Void 클로저를 받아 View가 화면에 표시될 때 실행하는 SwiftUI의 View 수식자입니다. 이 수식자는 .onDisappear 및 .task와 함께 SwiftUI 컴포넌트 생명주기 시스템의 일부입니다. Apple은 UIKit의 viewDidLoad를 대체하기 위해 SwiftUI iOS 13 및 watchOS 6 릴리스와 함께 onAppear를 도입했습니다.
구문적으로 .onAppear는 모든 View를 수정하고 작업이 첨부된 동일한 View를 반환합니다. SwiftUI 컴포저는 View가 계층 구조에 추가되어 렌더링 단계를 통과할 때 전달된 클로저를 한 번 호출합니다. View가 제거되었다가 다시 추가되면(예: 목록 스크롤 시) onAppear가 다시 호출됩니다 — 이 동작은 종종 예기치 않은 버그의 원인이 됩니다.
수식자의 기본 구문은 최소화되어 있습니다: 매개변수 없는 onAppear. SwiftUI에는 우선순위나 애니메이션을 전달하는 방법이 없습니다 — 클로저는 렌더링 직후 메인 스레드에서 동기적으로 실행됩니다.
struct ContentView: View {
var body: some View {
Text("Hello, SwiftUI!")
.onAppear {
print("View appeared on screen")
}
}
}
제한 사항: onAppear는 async/await를 직접 지원하지 않습니다. 클로저 내에서 비동기 작업을 수행하려면 Task {} 또는 Task.detached를 통해 호출되는 별도의 async/await 함수가 필요합니다. 이로 인해 onAppear는 .task 수식자에 비해 네트워크 요청에 덜 편리합니다.
.onAppear는 layout+render 단계에서 SwiftUI 렌더링 파이프라인에 내장됩니다. SwiftUI가 View 본문을 계산하고 계층 구조 변경을 감지하면 새로 추가된 모든 View에 대해 onAppear 콜백을 실행합니다. 호출 순서는 중첩을 따릅니다: 먼저 부모 onAppear, 그 다음 자식 요소입니다.
SwiftUI의 중요한 특징은 onAppear가 실제 화면 표시에 연결되지 않는다는 것입니다. 수식자는 View가 사용자에게 표시되는지 여부와 관계없이(예: ScrollView에서 화면 밖에 있는 경우) 계층 구조에 추가될 때 호출됩니다. 이는 viewWillAppear가 실제 표시 시에만 실행되는 UIKit과 SwiftUI를 구분합니다.
호출 순서는 부모 우선 규칙을 따릅니다: VStack 또는 NavigationView가 먼저 onAppear를 받고, 그 다음 각 자식 요소가 순서대로 받습니다. 이는 공유 리소스 초기화에 중요합니다: 자식 요소가 부모가 로드한 데이터에 의존하는 경우 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")
}
}
}
콘솔 출력: 먼저 Parent onAppear, 그 다음 순서대로 Child onAppear가 두 번 출력됩니다. 이 동작은 Apple에 의해 보장되며 모든 SwiftUI 버전(iOS 13–18)에서 안정적입니다.
.onAppear에는 컨테이너 및 탐색에 따라 여러 호출 시나리오가 있습니다. NavigationStack에서 onAppear는 새 컨트롤러가 push될 때마다 그리고 pop 시 — 루트 컨트롤러에 대해 실행됩니다. TabView에서 탭 전환은 표시된 탭에 onAppear를, 숨겨진 탭에 onDisappear를 호출합니다.
List 및 ScrollView에서 onAppear는 가시 영역에 들어가거나 사전 렌더링 버퍼에 있는 셀에 대해 호출됩니다. iOS 18은 스크롤보다 2~3 화면 앞서 있는 셀에 대해 onAppear를 호출할 수 있는 프리페치 메커니즘을 도입했습니다 — 이는 인지 속도를 높이지만 불필요한 네트워크 요청을 유발할 수 있습니다.
NavigationStack(iOS 16+)은 NavigationView와 다르게 화면 스택을 관리합니다. 새 화면을 push할 때 onAppear는 새 화면에서만 실행되며 현재 화면은 실제 제거까지 onDisappear를 받지 않습니다. Pop 시에는 반대 프로세스가 발생합니다: 떠나는 화면에서 onDisappear, 돌아오는 화면에서 onAppear.
| 시나리오 | onAppear | onDisappear |
|---|---|---|
| Push | 새 화면 | 없음 (화면이 스택에 남음) |
| Pop | 돌아오는 화면 | 떠나는 화면 |
| 탭 전환 | 새 탭 | 이전 탭 |
| 시트 닫기 | 부모 화면 | 열린 시트 |
실제 적용은 데이터 로드, 애니메이션 시작, 분석 전송의 세 가지 주요 범주를 다룹니다. 각 시나리오는 중복 호출 및 메모리 누수를 방지하기 위해 SwiftUI 생명주기 기능을 고려해야 합니다.
데이터 로드는 onAppear의 가장 일반적인 사용 사례입니다. 클로저 내에서 async 호출을 위한 Task가 생성되고 결과가 @State 또는 @StateObject에 저장됩니다. isLoading 플래그 또는 nil 검사를 사용하여 데이터가 이미 로드되었는지 확인하는 것이 중요합니다.
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()
}
}
}
}
재요청 방지는 중요한 관행입니다. SwiftUI가 View를 다시 생성하면(예: 화면 회전 시) 보호 장치 없이 onAppear가 다시 실행됩니다. 대안은 .task 수식자로, 이전 요청을 자동으로 취소합니다.
진입 애니메이션은 withAnimation 또는 animation 수식자를 통해 애니메이션을 트리거하는 상태 변수를 변경하기 위해 onAppear를 사용합니다. 일반적인 패턴: 초기 상태(opacity 0, offset 100), 나타날 때 최종 상태(opacity 1, offset 0)로 전환됩니다.
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
}
}
}
}
0.3초의 지연은 화면에 여러 카드가 있는 경우 순차적 나타남 효과를 만듭니다. 애니메이션 요소 목록의 경우 요소 인덱스를 지연 승수로 사용합니다.
.task는 iOS 15에 추가된 SwiftUI 수식자로 onAppear에서 비동기 작업의 문제를 해결합니다. onAppear와 달리 .task는 async 클로저를 받아들이고 자동으로 생명주기를 관리하며 View가 사라질 때 취소합니다. onAppear가 동기적으로 실행되는 반면 .task는 비동기 작업을 시작하고 SwiftUI가 onDisappear에서 취소할 수 있도록 합니다.
주요 차이점은 취소 관리입니다. .task가 async 작업을 생성하면 SwiftUI는 Task에 대한 참조를 저장하고 View가 계층 구조에서 제거될 때 자동으로 cancel()을 호출합니다. 내부에 Task {}가 있는 onAppear는 실행 중인 작업을 취소하지 않습니다 — View가 사라진 후에도 계속 실행되어 경쟁 조건 또는 할당 해제된 인스턴스에 쓰기를 유발할 수 있습니다.
| 특성 | .onAppear | .task |
|---|---|---|
| iOS 버전 | iOS 13+ | iOS 15+ |
| Async 지원 | Task {} 통해서만 | 네이티브 async/await |
| 자동 취소 | 없음 | View 사라질 때 |
| 재호출 | 나타날 때마다 | 기본적으로 한 번 |
| 동기 코드 | 예 | Async만 |
수식자 선택: 동기 작업(애니메이션, 분석, 로깅)에는 onAppear를 사용하세요. 비동기 데이터 로드(API, Core Data, 파일 시스템)에는 .task를 선호하세요 — 더 안전하고 깔끔합니다.
실수 1: View 재생성으로 인한 여러 번의 호출. SwiftUI가 View 본문을 다시 생성하면(상태 변경, 화면 회전) onAppear가 다시 호출될 수 있습니다. 해결책 — 로딩 플래그를 추가하거나 .equatable()을 사용하여 불필요한 다시 그리기를 방지합니다. SwiftLee(2025)에 따르면 프로덕션에서 SwiftUI 버그의 40%는 onAppear의 반복 호출과 관련이 있습니다.
실수 2: 강한 참조로 인한 메모리 누수. onAppear 클로저가 약한 참조 없이 self를 캡처하면 View와 순환 참조가 생성됩니다. SwiftUI는 View가 사라질 때 캡처된 객체의 nullify를 보장하지 않습니다. ViewModel 또는 서비스에는 캡처 목록 [weak self]를 사용하세요.
실수 3: 백그라운드 스레드에서 실행. onAppear는 메인 스레드에서 실행됩니다 — 이는 UI 작업에 적합합니다. 하지만 onAppear 내에서 Task를 시작하는 경우 @State 업데이트가 MainActor.run을 통해 이루어지는지 확인하세요. Swift 5.9 이상은 자동으로 MainActor로 돌아가지만 @MainActor를 명시적으로 지정하는 것이 좋습니다.
로딩 플래그를 사용한 패턴은 중복을 방지하는 가장 신뢰할 수 있는 방법입니다. 플래그를 @State 또는 @StateObject에 저장하고 수동 업데이트 시에만 재설정합니다. 대안은 onAppear 대신 .task를 사용하는 것입니다: .task는 async 작업이 이미 실행 중인 경우 기본적으로 다시 그리기 시 재시작되지 않습니다.
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()
}
}
}
}
자주 묻는 질문
viewDidLoad는 UIViewController의 수명 동안 한 번 호출되며 가시성과 무관합니다. .onAppear는 View가 계층 구조에 추가될 때마다 호출됩니다 — View가 제거되었다가 다시 추가되면 onAppear가 다시 실행됩니다. NavigationView에서 viewDidLoad는 초기화 중에 호출되는 반면 onAppear는 각 화면 표시 시 호출됩니다.
네, Task { await asyncFunction() } 래퍼를 통해 가능합니다. 그러나 async 작업의 경우 .task가 선호되는데, 자동으로 취소를 관리하고 수동으로 Task를 생성할 필요가 없습니다. .task는 View가 사라질 때 취소를 보장하여 누수를 방지합니다.
원인은 @State, @Published 또는 상위 구성 변경으로 인한 View 본문 재생성입니다. SwiftUI는 관찰 가능한 속성의 변경에 반응하여 View를 다시 그릴 수 있습니다. 또한 LazyVStack 및 List는 가시 영역에 접근하는 셀에 대해 onAppear를 호출하고 위로 스크롤할 때 다시 호출합니다.
네, .onAppear는 모든 SwiftUI 플랫폼에서 사용 가능합니다: iOS 13+, watchOS 6+, tvOS 13+, macOS 10.15+. 동작은 동일합니다: View가 계층 구조에 추가될 때 수식자가 호출됩니다. watchOS에서 onAppear는 앱이 대기 상태에서 활성화될 때 실행되므로 디자인에서 고려해야 합니다.
.onAppear는 매개변수를 받지 않습니다 — Void 클로저만 받습니다. 매개변수를 전달하려면 외부 변수를 캡처하는 클로저를 사용하세요. 대안적인 접근 방식은 ViewModifier 또는 .onChange의 동등물을 통해 매개변수가 있는 사용자 정의 onAppear 수식자를 만드는 것입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.