감정일기(가칭)

iOS 스플래시 화면에서 메인 화면으로 깨끗하게 전환하기— present 대신 rootViewController를 교체하는 방식

밤새는 탐험가89 2025. 11. 28. 14:35
728x90
SMALL

앱을 만들다 보면 이런 화면 흐름을 자주 구현하게 됩니다.

앱 실행 → 스플래시 화면 → 메인 화면

대부분의 초반 구현은 대략 이런 느낌일 거예요.

self.present(mainViewController, animated: true)

 

겉으로는 잘 동작합니다.
스플래시 화면이 잠깐 보이고, 그 다음에 메인 화면이 나타나죠.

그런데…
이 방식에는 구조적인 문제가 하나 있습니다.


1. 스플래시 화면을 present하면 생기는 문제

예를 들어, 이런 스플래시 뷰컨트롤러가 있다고 합시다.

class SplashViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        UIView.animate(withDuration: 0.75, animations: {
            // 로고 애니메이션 등
        }) { _ in
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
                self.moveToMain()
            }
        }
    }
    
    private func moveToMain() {
        let mainVC = MainViewController(homeVM: homeVM)
        let naviVC = UINavigationController(rootViewController: mainVC)
        naviVC.modalPresentationStyle = .fullScreen
        naviVC.modalTransitionStyle = .crossDissolve
        self.present(naviVC, animated: true)
    }
}

 

이 코드의 흐름은 다음과 같습니다.

  1. 앱 시작 시, SplashViewController가 window.rootViewController로 설정됨
  2. viewDidAppear에서 present를 이용해 메인 화면을 올림

이때, UIKit 내부 구조는 이렇게 생깁니다.

Window
 └── rootViewController: SplashViewController
        └── presentedViewController: UINavigationController
                └── rootViewController: MainViewController

 

즉:

  • 메인 화면은 스플래시 화면 위에 present된 상태
  • 스플래시 화면은 메모리에 그대로 남아 있고, view hierarchy에도 붙어 있음

그래서 이런 일들이 벌어질 수 있습니다.

  • 나중에 dismiss(animated: true) 같은 걸 잘못 호출하면
    👉 메인 화면이 사라지고 스플래시 화면이 다시 튀어나옴
  • 처음부터 앱의 시작점을 메인 화면으로 쓰고 싶은데,
    👉 항상 스플래시가 “밑바닥에 깔린 채”로 유지되는 구조

스플래시는 원래:

“앱이 켜질 때 딱 한 번 보여주고 사라지는 일회성 화면”

인데,
present를 쓰면:

“앱 전체 생명주기 동안 숨겨진 채 계속 붙어 있는 화면”
이 되어 버리는 거죠.


2. 정석적인 방법: rootViewController를 교체하기

이 문제를 해결하는 가장 깔끔한 방법은:

✅ 메인 화면을 “새로 올리는” 게 아니라,
앱의 시작점(rootViewController)을 교체하는 것

입니다.

핵심 아이디어

  1. 앱 시작 시 SplashViewController를 rootViewController로 사용
  2. 일정 시간이 지난 뒤,
    window.rootViewController를 UINavigationController(MainViewController)로 변경
  3. 이 순간부터는 스플래시는 계층 구조에서 제거되고, ARC에 의해 정리됨

코드는 다음과 같이 작성할 수 있습니다.

private func moveToMain() {
    let mainVC = MainViewController(homeVM: homeVM)
    let naviVC = UINavigationController(rootViewController: mainVC)
    naviVC.modalPresentationStyle = .fullScreen
    
    // 현재 윈도우 가져오기 (iOS 13+ 대응)
    guard let window = UIApplication.shared.connectedScenes
        .compactMap({ $0 as? UIWindowScene })
        .first?.windows.first else { return }

    // ✅ 스플래시 대신 메인 네비게이션 컨트롤러를 루트로 교체
    window.rootViewController = naviVC

    // ✅ 자연스러운 전환을 위한 크로스 디졸브 애니메이션
    UIView.transition(with: window,
                      duration: 0.4,
                      options: .transitionCrossDissolve,
                      animations: nil,
                      completion: nil)
}

 

이제 내부 구조는 이렇게 바뀝니다.

(교체 전)
Window
 └── rootViewController: SplashViewController

(교체 후)
Window
 └── rootViewController: UINavigationController
        └── rootViewController: MainViewController

 

이 방식의 장점

  • ✅ 스플래시 화면은 더 이상 window에 붙어 있지 않기 때문에
    → 자연스럽게 메모리에서 정리됨
  • ✅ 메인 화면이 앱의 진짜 시작점이 됨
    → 네비게이션 스택 구조가 훨씬 이해하기 쉬워짐
  • ✅ 나중에 dismiss()를 써도 스플래시가 갑자기 튀어나오는 일 없음
  • ✅ “스플래시 → 메인”이라는 일방향 흐름을 코드 구조가 그대로 반영

3. UIView.transition(with: window, ...)로 부드러운 애니메이션까지

rootViewController를 교체하는 것 자체는 순식간에 일어납니다.
이걸 그냥 교체만 하면 화면이 “뚝” 바뀌는 느낌이 나겠죠.

이때 사용하는 게 바로:

UIView.transition(with: window,
                  duration: 0.4,
                  options: .transitionCrossDissolve,
                  animations: nil,
                  completion: nil)

 

  • with: window → 앱 전체(Window)에 애니메이션을 적용
  • .transitionCrossDissolve → 기존 화면과 새 화면이 부드럽게 겹치며 페이드 전환
  • 별도의 애니메이션 코드를 작성하지 않아도,
    rootViewController 교체 자체를 전환 애니메이션으로 감싸는 효과

결과적으로 사용자는:

“스플래시 화면이 자연스럽게 사라지면서 메인 화면이 등장하는 느낌”

을 받지만,
개발자 입장에서는:

“앱의 루트(시작점)를 통째로 교체해서 구조적으로 깨끗한 상태로 유지”

하게 됩니다.


4. 정리

  • 스플래시 화면에서 메인 화면으로 갈 때
    present를 사용하면 스플래시가 뒤에 계속 남아 있는 상태가 됩니다.
  • 스플래시는 한 번만 보여주고 사라지는 화면이므로,
    아예 rootViewController를 메인으로 교체하는 방식이 더 적절합니다.
  • window.rootViewController = mainNav로 루트를 교체하면:
    • 스플래시는 계층에서 제거
    • 메인 화면이 진짜 시작 화면이 됨
  • UIView.transition(with:window, .transitionCrossDissolve, ...)를 사용하면
    전환도 자연스럽게 연출할 수 있습니다.

한 줄 요약:
“스플래시는 present하지 말고, rootViewController를 갈아끼우자.”

728x90
LIST