SwiftUI로 개발을 진행하다가 View 내부에서 직접적으로 프로퍼티에 접근할 때 onAppear를 한 번쯤은 사용한 경험이 있을 것이다.
가령, print(프로퍼티)처럼 View 내부에서 접근하면 이런 에러가 뜬다.
View를 반환해달라는 에러다.
이런 에러와 마주치지 않기 위해 View 내부에서 직접적으로 접근하지 않고, onAppear 클로저 내부에서 접근하는 방식을 택한다.
아무튼 이럴 때 자주 사용하는 onAppear는 SwiftUI View Life Cycle에 속하고, 오늘은 Life Cycle에 대해 알아보려고 한다.
SwiftUI에선 3개의 Life Cycle이 있고, 각각의 Appear, Update, Disappear 이다.
1. onAppear
onAppear은 View가 보여지기 전에 호출된다.
UIkit으로 치면 viewWillAppear라고 생각하는데 아래 링크에서는 viewDidAppear라고 설명하고 있다.
How to respond to view lifecycle events: onAppear() and onDisappear() - a free SwiftUI by Example tutorial
Was this page useful? Let us know! 1 2 3 4 5
www.hackingwithswift.com
실제로 공식 문서에서도 "before this view appears"로 나와있고, 뷰가 나타나기 전이니까 willAppear가 맞지 않을까 하는데...
(알고 게신분이 있으시면 도움 부탁드립니다 ㅠ)
2. onDisappear
그렇다면 Disappear는?
onAppear과 반대로 View가 사라진 뒤에 호출된다.
요건 UiKit에서의 viewDidDisappear라고 생각한다. 사라진 뒤에 호출되는 거니까?
근데 뭔가 이렇게 세트로 맞아야할 것 같은데
(SwiftUI) - (UIKit)
onAppear - viewWillAppear
onDisappear - viewWillDisappear
내 생각대로하면 이런 느낌이라
(SwiftUI) - (UIKit)
onAppear - viewWillAppear
onDisappear - viewDidDisappear
안 맞으니까 불편하다.
이 부분은 좀 더 알아봐야겠다.
3. NavigationStack
Update에 대해서는 나중에 다시 한 번 알아보도록 하고...
사실 오늘은 특정 뷰의 Life Cycle을 알아보다가 신기해서 적는 글이다.
뭔가 전환이 일어나거나 특정 뷰 위에 존재하는 뷰인 NavigationStack - NavigationLink, ModalSheet, Alert.
이 친구들에 대해서 알아봤다.
우선 첫 번째로, NavigationStack에서의 언제 호출되는지 알아보자.
import SwiftUI
// 첫 번째 뷰
struct FirstView: View {
var body: some View {
NavigationStack {
NavigationLink(destination: SecondView()) {
Text("다음으로 가볼까요?")
}
}
.onAppear {
print("FirstView Appear")
}
.onDisappear {
print("FirstView Disappear")
}
}
}
// 두 번째 뷰
struct SecondView: View {
var body: some View {
NavigationLink(destination: ThridView()) {
Text("다음으로 가볼까요?")
}
.onAppear {
print("SecondView Appear")
}
.onDisappear {
print("SecondView Disappear")
}
}
}
// 세 번째 뷰
struct ThridView: View {
var body: some View {
NavigationStack {
Text("세 번째까지 왔습니다.")
.onAppear {
print("ThirdView Appear")
}
.onDisappear {
print("ThirdView Disappear")
}
}
}
}
이렇게 코드를 작성하고 아까 위에서 봤던 Flow를 그대로 실행하면 어떤 결과가 출력될까?
위에서 얘기했던 것처럼 onAppear는 보여지기 전, onDisappear는 사라진 후에 호출된다고 했다.
그렇다면 다음 View의 onAppear가 먼저 출력되고 이후에 이전 View의 onDisappear가 출력되지 않을까?
FirstView Appear
SecondView Appear
FirstView Disappear
ThirdView Appear
SecondView Disppear
하지만 결과는 달랐다.
FirstView Disappear가 출력되지 않았다.
UI에서 사라졌는데 onDisappear가 출력되지 않는게 이상해서 디버깅을 진행했고, 실제로 UI Hierarchy를 출력해봤다.
사진을 보면 UIKitNavigationController와 NavigationStackHostingController가 동일하게 배치되어 있다.
NavigationLink로 화면이 전환되도, 그 기반을 이루는 NavigationStack(Controller)는 실제로 보이진 않지만 그대로 유지되고 있기에 onDisappear가 호출되지 않은 것이었다.
그렇다면 NavigationStack이 아닌, 내부에 위치하고 있는 NavigationLink에 적용하면 어떻게 될까?
struct FirstView: View {
var body: some View {
NavigationStack {
NavigationLink(destination: SecondView()) {
Text("다음으로 가볼까요?")
}
.onAppear {
print("FirstView Appear")
}
.onDisappear {
print("FirstView Disappear")
}
}
}
}
뭔가, 이번엔 제대로 동작할 것 같다는 느낌이 들었다.
이번엔 우리가 처음에 예상했던 것처럼 출력됐다!
NavigationStack에서 onAppear나 onDisappear를 사용할 필요가 있을 때는 내부에 위치하고 있는 View에서 접근하는 것이 좋을 듯 하다!
NavigationStack이 이렇게 작동하는 걸 보니까 다른 View들도 궁금해졌다.
4. Modal View
그 다음 타자는, Modal View였다.
Model View도 다른 View과 함께 존재하므로 뭔가 알아보고 싶었다.
struct FirstView: View {
@State private var isShow: Bool = false
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: SecondView()) {
Text("다음으로 가볼까요?")
}
Button(action: {
isShow.toggle()
}) {
Text("모달 꺼내기")
}
.onAppear {
print("FirstView Appear")
}
.onDisappear {
print("FirstView Disappear")
}
}
.sheet(isPresented: $isShow, onDismiss: {
print("ModalSheet Dismiss")
}) {
Text("나? 모달")
}
.onAppear {
print("ModelSheet Appear")
}
.onDisappear {
print("ModelSheet Disappear")
}
}
}
}
Model View가 보여지고 닫히는 Flow를 실행하면 어떤 결과가 출력 될까?
FirstView Appear
ModalSheet Appear
ModalSheet Dismiss
ModalSheet Disappear
하지만 달랐다...
FirstView Appear보다 ModalSheet Appear가 먼저 이뤄졌고 Modal이 사라졌을 때 Dismiss만 호출됐을 뿐, Disappear는 호출되지 않았다.
UI Hierarchy에서 Modal이 올라오기 전, 올라오고 난 후의 차이가 있음에도 ModalSheet Appear가 Modal이 실제로 올라왔을 때 출력되지 않는 이유에 대해서는 아직 잘 모르겠다.
(알고 게신 분 있으시면 도움 부탁드립니다 ㅠ)
그래서, 일단 NavigationStack처럼 내부에 존재하는 View에 onAppear와 onDisappear를 달아줬다.
struct FirstView: View {
@State private var isShow: Bool = false
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: SecondView()) {
Text("다음으로 가볼까요?")
}
Button(action: {
isShow.toggle()
}) {
Text("모달 꺼내기")
}
.onAppear {
print("FirstView Appear")
}
.onDisappear {
print("FirstView Disappear")
}
}
.sheet(isPresented: $isShow, onDismiss: {
print("ModalSheet Dismiss")
}) {
Text("나? 모달")
.onAppear {
print("ModelSheet Appear")
}
.onDisappear {
print("ModelSheet Disappear")
}
}
}
}
}
과연?
이번엔 정상적으로 작동한다!
NavigationStack과 마찬가지로, Sheet에서 onAppear나 onDisappear를 사용할 필요가 있을 때는 내부에 위치하고 있는 View에서 접근하는 것이 좋을 듯 하다!
5. Alert
마지막으로 Alert, 이 친구도 궁금해졌다.
struct FirstView: View {
@State private var isShow: Bool = false
@State private var isAlert: Bool = false
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: SecondView()) {
Text("다음으로 가볼까요?")
}
Button(action: {
isShow.toggle()
}) {
Text("모달 꺼내기")
}
.onAppear {
print("FirstView Appear")
}
.onDisappear {
print("FirstView Disappear")
}
}
.sheet(isPresented: $isShow, onDismiss: {
print("ModalSheet Dismiss")
}) {
VStack {
Text("나? 모달")
Button(action: {
self.isAlert.toggle()
}) {
Text("알랏 띄우기")
}
.alert("안녕", isPresented: $isAlert) {
Button("확인") {}
} message: {
Text("나? 알랏")
}
.onAppear {
print("Alert Appear")
}
.onDisappear {
print("Alert Disappear")
}
}
.onAppear {
print("ModelSheet Appear")
}
.onDisappear {
print("ModelSheet Disappear")
}
}
}
}
}
이번에도 앞서 봤던 다른 View들처럼 마지막에 modifer를 추가해줬다.
Alert이 보여지는 View인, Modal View가 보여질 때 Alert의 onAppear도 호출된다.
역시나 우리의 생각대로 움직이지 않는다.
그래서 기존에 View의 마지막에 추가해줬던 modifier를 View의 내부로 이동시켰다.
struct FirstView: View {
@State private var isShow: Bool = false
@State private var isAlert: Bool = false
var body: some View {
NavigationStack {
VStack {
NavigationLink(destination: SecondView()) {
Text("다음으로 가볼까요?")
}
Button(action: {
isShow.toggle()
}) {
Text("모달 꺼내기")
}
.onAppear {
print("FirstView Appear")
}
.onDisappear {
print("FirstView Disappear")
}
}
.sheet(isPresented: $isShow, onDismiss: {
print("ModalSheet Dismiss")
}) {
VStack {
Text("나? 모달")
Button(action: {
self.isAlert.toggle()
}) {
Text("알랏 띄우기")
.onAppear {
print("Alert Appear")
}
.onDisappear {
print("Alert Disappear")
}
}
.alert("안녕", isPresented: $isAlert) {
Button("확인") {}
} message: {
Text("나? 알랏")
}
}
.onAppear {
print("ModelSheet Appear")
}
.onDisappear {
print("ModelSheet Disappear")
}
}
}
}
}
과연?
이번엔 우리가 생각했던 대로 호출된다!
UI hierarchy가 궁금해서 출력해봤다.
역시 이번에도 Controller가 존재하는 것을 볼 수 있다.
Controller가 존재하는 뷰라고 할까?
이러한 뷰들은 뷰 자체에 Life cycle modifier를 달지 말고,
내부에 존재하는 뷰에 modifier를 달아야
우리가 의도했던 대로 작동한다.
지난 포스팅에서도 ScrollView 자체에 달지 않고, 내부에 존재하는 LectureItemView에 달아준 것처럼 말이다.
만약 Life cycle이 제대로 동작하지 않는다면, Controller에 달려있나 확인해 볼 것!
'💻 개발 > iOS' 카테고리의 다른 글
[iOS / SwiftUI] MapKit, 실시간으로 도로명 주소 변환하기 (2) | 2022.12.20 |
---|---|
[iOS / SwiftUI] 스크롤, 무한으로 즐겨요~ (LazyVStack으로 무한 스크롤 구현하기) (0) | 2022.11.28 |
[iOS / Swift] Swift 문자열 정복하기 (aka 'Character') (0) | 2022.11.03 |
[iOS / SwiftUI] 다양한 상태 프로퍼티들을 알아보자! (0) | 2022.10.24 |
[iOS / SwiftUI] 키보드가 사라지지 않아요...😩 (0) | 2022.10.21 |