본문 바로가기
감정일기(가칭)

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

by 밤새는 탐험가89 2025. 11. 28.
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