iOS
스플래시 화면을 활용한 홈화면 백화현상 개선하기
Danny Sung여기어때
2024년 12월 12일
원문에서 보기 ↗안녕하세요. 여기어때컴퍼니 iOS 개발팀 대니입니다.
앱을 처음 실행했을 때 사용자에게 보이는 첫 화면은 앱의 첫인상을 결정짓는 중요한 요소입니다. 이때 화면에 아무것도 표시되지 않는 빈 화면이 노출된다면 사용자는 앱이 느리거나 불안정하다고 느낄 수 있습니다. 이러한 빈 화면이 나타나는 현상을 ‘백화현상’이라고 하며, 이는 사용자 경험을 저해하고 앱에 대한 신뢰를 떨어뜨릴 수 있습니다.
이번 글에서는 스플래시 화면을 활용하여 초기 앱 진입시 홈 화면 로딩 중 발생하는 백화현상을 최소화하고 온보딩 플로우를 간소화함으로써 사용자 경험을 개선한 개발 과정을 공유해 보고자 합니다.
홈 화면의 백화현상 원인 파악
홈 화면의 백화현상은 네트워크 상태나 서버 응답 지연으로 인해 발생할 수 있는 문제입니다. 특히 앱의 UI가 Server Driven 방식으로 구성된 경우, 이러한 현상이 더욱 두드러질 수 있습니다.
Server Driven 방식이란 앱의 UI 레이아웃과 콘텐츠 구성을 서버에서 제공하는 데이터에 따라 동적으로 결정하는 구조를 말합니다. 이를 통해 서버에서 UI를 실시간으로 제어하거나 배포 없이 화면 구성을 변경할 수 있는 유연성을 제공합니다. 하지만 이 방식은 서버 응답에 의존하기 때문에, 응답이 지연되거나 실패하면 빈 화면이 표시되는 문제가 발생할 수 있습니다.
여기어때 앱 또한 Server Driven UI 구조로 되어 있어, 홈 화면을 구성하는 데이터가 서버 응답에 의존합니다. 따라서 네트워크 상태가 불안정하거나 서버 응답 시간이 길어지면, 빈 화면이나 로딩 상태가 길어지는 문제가 발생할 수 있습니다.
Launch Screen과 초기화 작업

Configure a launch screen storyboard

Configure a launch screen in an plist
앱 초기 로딩 시 기본적으로 제공되는 스플래시 화면인 Launch Screen은 정적 화면으로 구성됩니다. Launch Screen은 앱 로고나 이미지를 보여주는 용도로 사용되며, Storyboard나 정적 이미지 레이아웃으로 설정됩니다. 하지만 이 화면은 코드 실행이 불가능하다는 한계가 있어, 홈 화면 진입 전에 필요한 작업을 처리하려면 Launch Screen 이후에 동작할 별도의 ViewController를 만들어야 합니다.
여기어때 앱에서는 Launch Screen 이후 초기화 작업을 담당하는 별도의 InitializeViewController를 사용합니다. 이 화면은 앱 로고를 표시하는 1차 스플래시 이미지를 보여줍니다. 이후, API를 통해 2차 스플래시 이미지를 선택적으로 노출합니다. 2차 스플래시 이미지는 마케팅과 연관된 콘텐츠로, 최소 노출 시간이 지정되어 있습니다.
홈 화면까지 진입 과정

1차 스플래시 이미지가 노출된 이후, 앱 실행 시점에 필요한 API 호출, 초기 세팅 작업, 앱 사용 권한 요청, 업데이트 팝업 표시 등 홈 화면 진입 전에 필요한 다양한 작업이 진행됩니다. 이 작업들이 완료되면 2차 스플래시 이미지를 표시한 뒤, 홈 화면인 HomeViewController로 이동합니다.
홈 화면을 구성하는 데 필요한 데이터는 HomeViewController의 viewDidLoad 메소드에서 API를 호출하여 가져옵니다. 하지만 API 응답 데이터가 도착해 UI가 렌더링되기 전까지 사용자는 빈 화면을 보게 됩니다. 만약 네트워크 연결 상태가 좋지 않을 경우, 최악의 상황에서는 수십 초 동안 빈 화면이 표시되어 사용자가 앱이 멈췄다고 오해할 수 있습니다.

홈 화면 백화현상 개선
홈 화면의 백화현상을 최소화하고 사용자 경험을 개선하기 위해, 홈 화면이 실제로 표시되기 전에 API를 호출하여 빈 화면 노출 시간을 줄이는 방법을 고민했습니다.
1. 스플래시 화면에서 홈 화면의 API를 미리 요청하고 응답이 오면 홈 화면으로 이동
첫 번째 방법은 스플래시 화면에서 홈 화면 API를 직접 호출하여 응답 데이터를 전달하거나, 홈 화면의 API 요청 메서드를 직접 호출한 뒤 응답이 완료되면 홈 화면으로 이동하는 방식입니다.
이 방식은 API 응답이 완료된 상태에서 홈 화면을 표시하기 때문에 화면 이동 후 데이터를 기반으로 UI를 바로 렌더링할 수 있다는 장점이 있습니다. 하지만 홈 화면 API를 외부에서 호출하고 관리해야 하므로 책임 분리가 어렵고 유지보수가 복잡해질 수 있습니다. 또한, 홈 화면이 보여진 이후 응답 데이터를 처리해 화면을 렌더링하는 동안 화면이 깜빡거리거나 잠시 보여지는 백화현상 문제를 완전히 해결할 수는 없었습니다.
2. 홈 화면 위에 스플래시 화면을 모달로 띄워놓고 API 응답이 오면 스플래시 화면을 dismiss
두 번째 방법은 홈 화면을 띄우면서 동시에 2차 스플래시 화면을 모달로 덮어, 사용자에게는 스플래시 화면만 보이도록 하는 방식입니다. 앱 구조에 따라 화면을 모달로 띄우는 방법은 달라질 수 있습니다.
navigationController나tabBarController에서 스플래시 화면을present한 후,HomeViewController를push하는 방식HomeViewController를 먼저push한 후,HomeViewController의viewWillAppear에서 스플래시 화면을present하는 방식

이 방식의 주요 특징은 홈 화면이 스플래시 화면 뒤에서 API 요청 및 UI 업데이트 작업을 수행하는 동안, 사용자는 여전히 스플래시 화면을 보고 있다고 인지한다는 점입니다. 2차 스플래시 화면은 API 응답이 도착한 후 dismiss되며 홈 화면이 사용자에게 자연스럽게 표시됩니다. 첫 번째 방법과 비교했을 때, 두 번째 방법은 홈 화면의 API 호출과 응답을 외부에서 관리하지 않아도 되므로 책임이 명확하며, 유지보수에도 유리합니다.
상황에 따라 스플래시 화면에는 Launch Screen과 동일한 앱 로고, 애니메이션, 마케팅 이미지 등 다양한 UI를 표시할 수 있습니다. 또한, 일정 시간 동안 스플래시 화면을 노출시킬 수도 있으며, 시간을 설정하지 않고 API 응답을 확인한 후 즉시 화면을 전환하는 방식도 선택할 수 있습니다.
최소 노출 시간을 설정한 경우, 해당 시간이 지나기 전에 API 응답이 오고 UI 업데이트까지 이루어졌다면, 스플래시 화면이 dismiss됨과 동시에 즉시 완전한 홈 화면을 보여줄 수 있습니다. 만약 네트워크 상태가 좋지 않아 API 응답이 최소 노출 시간 이후에 도착하더라도, 응답 전까지 스플래시 화면을 유지하므로 사용자 입장에서 백화현상을 인지하기 어렵습니다.

API 응답 이후 화면 이동 처리 코드 예시
홈 화면 및 스플래시 화면을 띄운 곳에서는 Delegate나 Notification, RxSwift 등을 사용하여 API응답 여부를 넘겨받아야 합니다. 예를 들어, Delegate를 사용하여 아래와 같이 구현할 수 있습니다.
public protocol HomeControllerDelegate: AnyObject {
func homeScreenDataDidLoad()
}
weak var delegate: HomeControllerDelegate?
...
fetchHomeData()
.subscribe(onNext: { [weak self] _ in
guard let self else { return }
delegate?.homeDataDidLoad()
})
.disposed(by: disposeBag)
홈 화면에서 API 응답을 받은 이후 delegate의 homeDataDidLoad를 호출하여 응답이 왔음을 전달합니다.
var isHomeDataLoadedSubject: BehaviorSubject<Bool> = BehaviorSubject(value: false)
...
extension BaseTabBarController: HomeControllerDelegate {
func homeDataDidLoad() {
isHomeScreenDataLoadedSubject.onNext(true)
}
HomeControllerDelegate를 채택한 BaseTabBarController에서는 isHomeScreenDataLoadedSubject로 응답완료 값을 전달합니다.
여기어때 앱에서는 마케팅 이미지 노출을 위해 일정 시간 동안 스플래시 화면을 유지해야 합니다. 이는 RxSwift의 delaySubscription Operators를 사용하여 일정 시간 동안 구독을 지연시키는 방식으로 간단하게 구현할 수 있습니다.
isHomeDataLoadedSubject
.delaySubscription(.milliseconds(1000), scheduler: MainScheduler.asyncInstance)
.take(until: { $0 }, behavior: .inclusive)
.subscribe(onNext: { [weak self] _ in
guard let self else { return }
coordinator.dismissSplashController(
presentingViewController: self,
animated: false
) {
...
}
})
.disposed(by: disposeBag)
위 코드에서는 isHomeDataLoadedSubject를 구독할 때, delaySubscription을 사용하여 최소 노출 시간인 1초 동안 구독을 지연하도록 설정했습니다. 이 시간 동안 이벤트가 들어와도 구독은 지연되므로, 스플래시 화면이 최소 1초 동안 유지됩니다. 이후 isHomeDataLoadedSubject에서 true 값이 방출되었다면 SplashController를 dismiss하고, 그 이후 필요한 작업들을 수행합니다. 화면이 dismiss된 후에는 더 이상 이벤트를 관찰할 필요가 없으므로 takeUntil을 사용하여 true 값이 들어오면 스트림을 종료하도록 설정했습니다.
홈 화면 UI를 구성하기 위해 여러 API 응답이 필요한 경우, 이를 zip 연산자로 묶어서 처리할 수도 있습니다. 하지만, 이 방식은 하나의 API 응답이 느릴 경우 스플래시 화면이 계속 유지되는 문제가 발생할 수 있습니다. 이를 방지하기 위해, 기본 UI를 구성하는 데 필요한 핵심 API를 기준으로 화면 전환을 진행하고, 나머지 API는 응답이 도착하는 대로 순차적으로 UI를 업데이트하도록 처리했습니다.
온보딩 플로우 개선
홈화면 백화현상 개선과 함께 여기어때 앱의 온보딩 플로우도 함께 개선이 되었습니다. 이번 작업으로 개선된 온보딩 플로우를 간단히 소개하며 마무리하도록 하겠습니다.
기존 온보딩 플로우
기존에는 앱 다운로드 후 처음 실행하여 홈 화면에 도달하기까지 여러 단계를 거쳐야 했습니다. 권한 안내 팝업이 연이어 나타났고, 사용자는 권한 허용 여부를 선택한 뒤 인트로 화면을 스와이프해야 했습니다. 이후, 로그인 버튼을 누르거나 ‘나중에 로그인하기’ 버튼을 선택해야만 홈 화면에 접근할 수 있었습니다.

이러한 절차는 처음 앱을 사용하는 사용자들에게 다소 번거롭고 복잡하게 느껴질 수 있었습니다. 가장 큰 문제는, 온보딩 화면을 스와이프해야 다음 단계로 이동할 수 있다는 점이 직관적이지 않았고, 로그인 버튼 또한 눈에 잘 띄지 않아 사용자가 혼란을 겪을 가능성이 많았다는 것입니다.
변경된 온보딩 플로우
이러한 사용자의 불편을 줄이기 위해 온보딩 플로우를 전반적으로 간소화했습니다.

기존의 로그인이나 회원가입 과정을 없애고, 앱 실행 후 바로 홈 화면으로 이동하도록 변경했습니다. 이를 통해 사용자는 숙소를 빠르게 탐색하고 예약을 진행할 수 있습니다. 또한, 권한 설정 안내 화면을 추가하여 앱이 요청하는 각 권한의 필요성과 사용 목적을 명확히 전달함으로써, 사용자가 권한 요청을 더 쉽게 이해하고 설정할 수 있도록 했습니다
마무리
이번 작업을 통해 사용자 경험을 저해하는 백화현상을 개선하고, 온보딩 플로우를 최적화함으로써 사용자들이 겪던 불편을 줄이고 앱의 첫인상을 보다 긍정적으로 만들 수 있었습니다. 비록 작은 변화처럼 보일 수 있지만, 이를 통해 사용자에게 더 나은 경험을 제공할 수 있다는 점에서 의미 있는 개선이라고 생각합니다.
이 글이 비슷한 문제를 고민 중인 분들께 작은 도움이 되길 바랍니다.
감사합니다!