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)
}
}
이 코드의 흐름은 다음과 같습니다.
- 앱 시작 시, SplashViewController가 window.rootViewController로 설정됨
- viewDidAppear에서 present를 이용해 메인 화면을 올림
이때, UIKit 내부 구조는 이렇게 생깁니다.
Window
└── rootViewController: SplashViewController
└── presentedViewController: UINavigationController
└── rootViewController: MainViewController
즉:
- 메인 화면은 스플래시 화면 위에 present된 상태
- 스플래시 화면은 메모리에 그대로 남아 있고, view hierarchy에도 붙어 있음
그래서 이런 일들이 벌어질 수 있습니다.
- 나중에 dismiss(animated: true) 같은 걸 잘못 호출하면
👉 메인 화면이 사라지고 스플래시 화면이 다시 튀어나옴 - 처음부터 앱의 시작점을 메인 화면으로 쓰고 싶은데,
👉 항상 스플래시가 “밑바닥에 깔린 채”로 유지되는 구조
스플래시는 원래:
“앱이 켜질 때 딱 한 번 보여주고 사라지는 일회성 화면”
인데,
present를 쓰면:
“앱 전체 생명주기 동안 숨겨진 채 계속 붙어 있는 화면”
이 되어 버리는 거죠.
2. 정석적인 방법: rootViewController를 교체하기
이 문제를 해결하는 가장 깔끔한 방법은:
✅ 메인 화면을 “새로 올리는” 게 아니라,
✅ 앱의 시작점(rootViewController)을 교체하는 것
입니다.
핵심 아이디어
- 앱 시작 시 SplashViewController를 rootViewController로 사용
- 일정 시간이 지난 뒤,
window.rootViewController를 UINavigationController(MainViewController)로 변경 - 이 순간부터는 스플래시는 계층 구조에서 제거되고, 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
'감정일기(가칭)' 카테고리의 다른 글
| ⚒️ 커스텀 네비게이션바 셀(customNavigationBarCell)이 statusBar에 가리는 문제.... (0) | 2025.12.02 |
|---|---|
| 👍 네비게이션 UI(로고 + 앱 이름 + 우측 버튼들)를 컬렉션뷰 + 컴포지셔널 레이아웃 구조 안의 커스텀 셀 형태로 넣기 (0) | 2025.12.02 |
| 🍋 UIKit에서 토스트 메시지(Toast Message) 구현하기 (0) | 2025.11.26 |
| 📘 MVVM에서 PassthroughSubject로 화면 이동 이벤트 처리하기 (0) | 2025.11.24 |
| 📅 Swift로 "주차(Week Number)" 계산하는 법 완전 정리 (0) | 2025.11.21 |