본문 바로가기

프로젝트/LookForRealBurger

ViewController의 Lifecycle과 Modal Present 이슈

개발을 진행하면서 TabBarController의 특정 탭에서 WriteViewController
present() 방식으로 모달 띄우는 과정에서 Xcode 경고 메시지가 발생한 경험이 있었다.

 

이 글에서는 경고 메시지의 원인, 생명주기의 중요성, 그리고 해결 과정을 다뤄보려고한다.

1️⃣ 문제

  1. TabBarController의 두 번째 탭 선택.
  2. EmptyViewController가 로드됨.
  3. EmptyViewController가 로드되자마자 WriteViewController를 present()로 모달 띄움.
  • 발생한 Xcode 경고 메시지

Presenting view controller <UINavigationController: 0x104815e00> from detached view controller <LookForRealBurger.EmptyPresentViewController: 0x10360e280> is not supported, and may result in incorrect safe area insets and a corrupt root presentation. Make sure <LookForRealBurger.EmptyPresentViewController: 0x10360e280> is in the view controller hierarchy before presenting from it. Will become a hard exception in a future release. 
    • 📌 경고 메시지
      • 이 경고의 핵심은 ViewControllerView 계층에 완전히 포함되지 않은 상태에서 present()를 호출하고 있다는 것이다
  • ⚠️ 발생 가능한 문제
    • 잘못된 Safe Area Insets: 레이아웃이 비정상적으로 표시될 수 있음
    • 루트 뷰 손상 가능성: 예상치 못한 크래시 발생 가능
    • iOS의 향후 버전에서 크래시 가능성 경고

 

2️⃣ 문제 분석

viewWillAppear(_ animated: Bool) 시점에서 present()를 호출했기 때문이다.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let vc = WriteReviewScene.makeView(
        tabBar: tabBarController
    )
    let nav = UINavigationController(rootViewController: vc)
    nav.modalPresentationStyle = .fullScreen
    present(nav, animated: true)
}

 

iOS ViewController 생명주기

  • viewWillAppear:
    • 뷰가 화면에 나타나기 직전 호출된다.
    • 즉, 화면 전환이 일어나기 직전에 호출.
    • 아직 뷰 계층에 완전히 추가되지 않은 상태.
  • viewIsAppearing:
    • 뷰 계층에 추가된 후 호출된다.
NOTE
viewWillAppear는 뷰가 아직 계층에 완전히 포함되지 않은 시점이라서,
이 상태에서 present()를 호출하면 경고가 발생할 수 있다.

 

3️⃣ 해결을 위한 접근 방식

1. DispatchQueue.main.async 사용

  • viewWillAppear에서 present()를 사용하는 대신, 비동기적으로 지연시키는 방식.
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        DispatchQueue.main.async { [weak self] in
        	guard let self else { return }
            let vc = WriteReviewScene.makeView(
            	tabBar: tabBarController
        	)
            let nav = UINavigationController(rootViewController: vc)
            nav.modalPresentationStyle = .fullScreen
            present(nav, animated: true)
        }
    }
  • 장점
    • 경고 메시지가 사라짐
  • 단점
    • 직관적이지 못해 유지보수하기 어려움
    • viewWillAppear에서 불필요한 비동기 호출을 사용

2. viewIsAppearing 사용 (권장)

  • 뷰 계층에 완전히 추가된 상태인 viewIsAppearing에서 present()를 호출하는 방식.
    override func viewIsAppearing(_ animated: Bool) {
        super.viewIsAppearing(animated)
        let vc = WriteReviewScene.makeView(
            tabBar: tabBarController
        )
        let nav = UINavigationController(rootViewController: vc)
        nav.modalPresentationStyle = .fullScreen
        present(nav, animated: true)
    }
  • 장점
    • 생명주기 원칙 준수: 뷰 계층이 완전히 설정된 후 Present
    • 경고 메시지 사라짐
    • 안정적인 뷰 전환

 

4️⃣ 결과 및 성과

  • Xcode 경고 제거: viewIsAppearing 사용으로 경고 완전히 제거.
  • 안정성 확보: 잘못된 Safe Area Insets 및 크래시 방지.
  • 사용자 경험 개선: 안정적인 화면 전환으로 UX 강화.
  • 코드 품질 향상: 생명주기를 준수하여 유지보수성 개선.
NOTE
왜 viewIsAppearing가 적합한가?
Apple의 공식 문서에서는, "뷰가 화면에 보여지기 직전의 시점"
"뷰의 frame, bounds, safe area insets, margins 등이 모두 설정된 상태" 로
이미 뷰 계층에 포함된 상태로 컨텐츠를 업데이트 하기에 적합한 상태라고 설명하고 있으며
이는 뷰가 완전히 계층에 추가된 후 화면 전환을 진행하는 것이 안전하고 안정적이라는 것을 의미한다.

 

5️⃣ 결론

  1. ViewController 생명주기를 깊이 이해하는 것이 중요하다.
  2. viewWillAppear는 뷰가 완전히 계층에 추가되기 전 시점이다.
  3. viewIsAppearing는 뷰가 완전히 계층에 추가된 후 호출되어 모달 전환에 적합하다.

👉 구글링과 경고 메시지에 의존하지 말고, 생명주기와 뷰 계층을 이해하는 것이 중요하다.