고도고도
🍎🍏
고도고도
전체 방문자
13,424
오늘
31
어제
64
  • 분류 전체보기 (170)
    • 🔨 프로젝트 (0)
      • TP 1 (0)
      • WhiteHCCTV (0)
      • FootPrint (0)
    • 💻 개발 (61)
      • iOS (28)
      • Android (6)
      • Kotlin (4)
      • Flutter (9)
      • Node.js (5)
      • Architecture (1)
      • 오늘의 삽질 (7)
      • 에러와의 동침 (1)
    • ✏️ 알고리즘 (6)
      • Graph (6)
      • String (0)
      • Sort (0)
    • ✍️ 코테 준비 (44)
      • Math (1)
      • Implementation (3)
      • String (3)
      • Brute Force (5)
      • Back Tracking (7)
      • Greedy (0)
      • Dynamic Programming (13)
      • Binary Search (1)
      • DFS, BFS (5)
      • Shortest Path (2)
      • Two Pointer (4)
      • MST (0)
    • 📚 CS (6)
      • Operating System (6)
    • ⛹️ 라이프 (53)
      • 2020 겨울방학 모칵코(팀) (13)
      • 2020 겨울방학 모각코(개인) (13)
      • 2021 여름방학 모칵코(팀) (8)
      • 2021 여름방학 모각코(개인) (7)
      • 코딩 테스트 (1)
      • 회고 (10)

블로그 메뉴

  • 홈
  • 깃허브
  • 링크드인

공지사항

인기 글

  • [NCSOFT] 2022 엔씨소프트 썸머 인턴 후기 - 1⋯
    2022.08.10
    [NCSOFT] 2022 엔씨소프트 썸머 인턴 후기 - 1⋯
  • [Flutter] SingleChildScrollView,⋯
    2021.08.18
    [Flutter] SingleChildScrollView,⋯
  • [iOS / SwiftUI] MapKit, 실시간으로 도로⋯
    2022.12.20
    [iOS / SwiftUI] MapKit, 실시간으로 도로⋯
  • [Android] 백그라운드에서 소켓 통신으로 이벤트 수신⋯
    2022.06.08
    [Android] 백그라운드에서 소켓 통신으로 이벤트 수신⋯
  • [iOS / SwiftUI] OnAppear, OnDisa⋯
    2022.12.01
    [iOS / SwiftUI] OnAppear, OnDisa⋯

최근 댓글

  • https://developer.apple.com/docu⋯
    고도고도
  • 게시글 잘 보았습니다. 혹시 주소에서 구를 가지고 오시는⋯
    나그네
  • 혹시 댓글이 안보이는데 .. y2e010924@naver.⋯
    eun
  • 글 솜씨가 뛰어나시네요! 좋은 글 잘 보고 갑니다 다음에도⋯
    alpha-traveler
  • NC......가슴이...웅장해집니다.......🤯
    이상한핑구 🐧

최근 글

  • [Architecture] MVVM + Clean Arch⋯
    2023.01.07
    [Architecture] MVVM + Clean Arch⋯
  • [iOS / SwiftUI] MapKit, 실시간으로 도로⋯
    2022.12.20
    [iOS / SwiftUI] MapKit, 실시간으로 도로⋯
  • [iOS / SwiftUI] OnAppear, OnDisa⋯
    2022.12.01
    [iOS / SwiftUI] OnAppear, OnDisa⋯
  • [에러와의 동침] 22년 11월 4주차
    2022.11.28
    [에러와의 동침] 22년 11월 4주차
  • [iOS / SwiftUI] 스크롤, 무한으로 즐겨요~ (⋯
    2022.11.28
    [iOS / SwiftUI] 스크롤, 무한으로 즐겨요~ (⋯

티스토리

hELLO · Designed By 정상우.
고도고도

🍎🍏

[iOS / SwiftUI] OnAppear, OnDisappear는 언제 호출될까?
💻 개발/iOS

[iOS / SwiftUI] OnAppear, OnDisappear는 언제 호출될까?

2022. 12. 1. 00:24

SwiftUI로 개발을 진행하다가 View 내부에서 직접적으로 프로퍼티에 접근할 때 onAppear를 한 번쯤은 사용한 경험이 있을 것이다.

 

가령, print(프로퍼티)처럼 View 내부에서 접근하면 이런 에러가 뜬다.

 

View를 반환해달라는 에러다.

이런 에러와 마주치지 않기 위해 View 내부에서 직접적으로 접근하지 않고, onAppear 클로저 내부에서 접근하는 방식을 택한다.

 

아무튼 이럴 때 자주 사용하는 onAppear는 SwiftUI View Life Cycle에 속하고, 오늘은 Life Cycle에 대해 알아보려고 한다.

SwiftUI에선 3개의 Life Cycle이 있고, 각각의 Appear, Update, Disappear 이다.

https://www.vadimbulavin.com/swiftui-view-lifecycle

 

1. onAppear

https://developer.apple.com/documentation/swiftui/view/onappear(perform:)

 

onAppear은 View가 보여지기 전에 호출된다.

UIkit으로 치면 viewWillAppear라고 생각하는데 아래 링크에서는 viewDidAppear라고 설명하고 있다.

 

https://www.hackingwithswift.com/quick-start/swiftui/how-to-respond-to-view-lifecycle-events-onappear-and-ondisappear

 

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

https://developer.apple.com/documentation/swiftui/view/ondisappear(perform:)

 

그렇다면 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
    '💻 개발/iOS' 카테고리의 다른 글
    • [iOS / SwiftUI] MapKit, 실시간으로 도로명 주소 변환하기
    • [iOS / SwiftUI] 스크롤, 무한으로 즐겨요~ (LazyVStack으로 무한 스크롤 구현하기)
    • [iOS / Swift] Swift 문자열 정복하기 (aka 'Character')
    • [iOS / SwiftUI] 다양한 상태 프로퍼티들을 알아보자!
    고도고도
    고도고도
    iOS 꿀잼
    댓글쓰기
    [iOS / SwiftUI] MapKit, 실시간으로 도로명 주소 변환하기
    다음 글
    [iOS / SwiftUI] MapKit, 실시간으로 도로명 주소 변환하기
    [iOS / SwiftUI] 스크롤, 무한으로 즐겨요~ (LazyVStack으로 무한 스크롤 구현하기)
    이전 글
    [iOS / SwiftUI] 스크롤, 무한으로 즐겨요~ (LazyVStack으로 무한 스크롤 구현하기)

    티스토리툴바