모달 프레젠테이션은 현재 컨텍스트 위에 화면을 표시하고 이전 인터페이스와의 상호 작용을 차단하는 방법입니다. 모바일 개발에서 모달 창은 데이터 입력, 작업 확인, 인증 및 옵션 선택과 같은 집중 작업에 사용됩니다. Apple HIG, 2025에 따르면 모달 프레젠테이션은 애플리케이션의 탐색 시나리오 중 20%를 넘지 않아야 합니다. Android에서는 DialogFragment, BottomSheet 및 특정 실행 플래그가 있는 Activity를 통해 모달이 구현됩니다.
핵심 포인트
모달 프레젠테이션은 새 화면이 현재 화면 위에 나타나 일시적으로 부모 콘텐츠와의 상호 작용을 차단하는 탐색 패턴입니다. 사용자는 이전 상태로 돌아가기 위해 모달 작업(취소, 저장, 닫기)을 명시적으로 완료해야 합니다.
모달은 인지 작업을 해결합니다. 나머지 인터페이스의 방해 없이 사용자의 주의를 단일 작업에 집중시킵니다. 이는 등록 양식, 확인 대화 상자, 파일 선택 및 타사 서비스를 통한 인증에 매우 중요합니다. Apple의 Human Interface Guidelines은 계속 진행하기 전에 완료해야 하는 작업에만 모달을 사용할 것을 권장합니다.
웹 모달 창과 달리 모바일 모달 프레젠테이션은 전체 화면(전체 화면 차지) 또는 부분(Page Sheet, Bottom Sheet)이 될 수 있습니다. 유형 선택은 작업 컨텍스트와 플랫폼 규칙에 따라 달라집니다. iOS는 대부분의 시나리오에서 Page Sheet를 선호하며 Full Screen은 비디오 플레이어 및 사진 편집기용으로 남깁니다.
Push 프레젠테이션(스택 탐색)은 탐색 스택에 화면을 추가하고 자동으로 뒤로 가기 버튼을 표시합니다. 사용자는 언제든지 이전 화면으로 돌아갈 수 있습니다. 반면 모달 프레젠테이션은 명시적 완료가 필요합니다. 뒤로 가기 버튼이 없거나 이전 화면으로 돌아가는 대신 모달 창을 닫습니다.
모달과 Push 프레젠테이션의 주요 차이점: 모달 프레젠테이션은 데이터 손실 없이 뒤로 가기 탐색을 차단하고, 닫기 위한 작업(Save, Cancel, Done)이 필요하며, 일반적으로 별도의 작업을 나타냅니다. Push 프레젠테이션은 탐색 계층을 유지하고, 자동으로 뒤로 가기 버튼을 추가하며, 순차적 콘텐츠 보기에 적합합니다.
| 특성 | 모달 프레젠테이션 | Push 프레젠테이션 |
|---|---|---|
| 뒤로 가기 차단 | 예, 명시적 작업 필요 | 아니오, 뒤로 가기 버튼 항상 사용 가능 |
| 일반적 사용 | 양식, 인증, 선택 | 상세 보기, 탐색 |
| 애니메이션 | 아래에서 위로(iOS), 슬라이드(Android) | 오른쪽에서 왼쪽으로(iOS) |
| 탐색 스택 | 메인 스택에 추가되지 않음 | 스택에 추가됨 |
실제로 Modal과 Push의 선택은 컨텍스트에 따라 다릅니다. 계속하기 전에 사용자가 완료해야 하는 작업에는 모달을 사용하고, 순차적 콘텐츠 탐색에는 Push를 사용하는 것이 권장됩니다. 같은 화면에서 패턴을 혼합하면 혼란을 초래하고 UX가 저하됩니다.
iOS는 UIModalPresentationStyle 열거형을 통해 여러 모달 프레젠테이션 스타일을 제공합니다. UIKit은 .fullScreen(전체 화면), .pageSheet(상단 여백 카드), .formSheet(iPad의 중앙 창) 및 .automatic(컨텍스트에 따라 시스템 선택)을 지원합니다. iOS 13 이후 기본 스타일은 .automatic이며 iPhone에서는 .pageSheet를 선택합니다.
모달 프레젠테이션을 위한 기본 UIKit 메서드는 present(_:animated:completion:)입니다. 메서드를 호출하는 컨트롤러는 presentingViewController가 되고 새 컨트롤러는 presentedViewController가 됩니다. 닫기는 dismiss(animated:completion:)을 통해 수행됩니다. SwiftUI는 유사한 동작을 위해 .sheet 수정자를 제공합니다.
SwiftUI의 선언적 접근 방식은 .sheet 및 .fullScreenCover 수정자를 사용합니다. 전자는 Page Sheet를 만들고 후자는 전체 화면 모달 프레젠테이션을 만듭니다. 둘 다 모달 창의 표시 여부를 제어하는 Bool 또는 식별 가능한 개체에 대한 바인딩을 허용합니다. 바인딩이 false로 설정되거나 환경에서 dismiss가 호출되면 닫힙니다.
struct ContentView: View {
@State private var showModal = false
var body: some View {
Button("양식 열기") {
showModal = true
}
.sheet(isPresented: $showModal) {
RegistrationForm()
}
}
}
struct RegistrationForm: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
Button("저장") { dismiss() }
}
}
Android에는 iOS와 같은 모달 프레젠테이션을 위한 단일 API가 없습니다. 대신 플랫폼은 여러 메커니즘을 제공합니다: 대화 상자 창용 DialogFragment, 하단 패널용 BottomSheetDialogFragment, 모달 화면용 NEW_TASK 및 CLEAR_TOP 플래그가 있는 Activity. Jetpack Compose는 모든 유형의 모달 창에 대해 통합된 Dialog 구성 요소를 도입했습니다.
DialogFragment는 Android SDK에서 모달 창의 기본 클래스입니다. 대화 상자의 수명 주기를 관리하고, 화면 회전을 처리하며, 상태를 저장합니다. 프래그먼트는 탐색 스택을 차단하지 않고 Activity 위에 표시됩니다. 닫기는 dismiss()를 통해 또는 setCancelable(true)인 경우 대화 상자 영역 밖을 탭하여 수행됩니다.
BottomSheetDialogFragment는 아래에서 올라오는 패널로 모달 창을 표시합니다. 이 패턴은 Material Design에서 옵션 선택, 공유 및 빠른 작업에 널리 사용됩니다. BottomSheet는 고정 높이 또는 확장 가능(피크 높이 + 전체 높이)할 수 있습니다. Compose에서는 Material3 라이브러리의 ModalBottomSheet가 사용됩니다.
@Composable
fun ModalScreen(onDismiss: () -> Unit) {
Dialog(onDismissRequest = onDismiss) {
Card(
modifier = Modifier.padding(16.dp)
) {
Column {
Text("모달 양식", style = MaterialTheme.typography.headlineSmall)
Button(onClick = onDismiss) {
Text("닫기")
}
}
}
}
}
모달 창은 강력한 UX 도구이지만 과도하게 사용하면 사용자 경험이 저하됩니다. Apple HIG와 Google Material Design은 권장 사항에 동의합니다: 모달은 집중 작업에 사용해야 하며 전체 탐색 작업의 20%를 초과하지 않아야 합니다.
모달 창은 다음 시나리오에 적합합니다: 데이터 입력(등록 양식, 프로필), 확인(삭제, 제출), 선택(날짜 선택기, 파일 관리자) 및 인증(OAuth, Firebase Auth). 작업 시간이 30초 미만이고 컨텍스트 차단이 필요한 경우 모달을 선택하세요.
다음 용도로 모달 프레젠테이션을 사용하지 마세요: 순차적 콘텐츠 보기(Push 사용), 오류 표시(Toast 또는 Snackbar 사용), 명시적 사용자 요청 없이 광고 및 프로모션 제공. Material Design은 중첩된 모달 창을 피할 것을 권장합니다. 이는 사용자를 혼란스럽게 하고 탐색 계층을 방해합니다.
텍스트 필드가 있는 모달 창의 경우 키보드 포커스 손실을 처리해야 합니다. 키보드가 나타나면 모달 창이 위로 이동하여 사용자가 입력 중인 텍스트를 볼 수 있어야 합니다. iOS의 UIKeyboardWillShowNotification과 Android의 adjustResize가 이 작업을 해결합니다.
두 플랫폼에서의 모달 프레젠테이션 구현을 살펴보겠습니다. Swift 예제는 닫기 처리를 위한 델리게이트와 함께 UIModalPresentationStyle.pageSheet 구성을 보여줍니다. Kotlin 예제는 사용자 정의 레이아웃과 상태 보존이 있는 DialogFragment를 보여줍니다.
let modalVC = ModalViewController()
modalVC.modalPresentationStyle = .pageSheet
if let sheet = modalVC.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
}
modalVC.presentationController?.delegate = self
present(modalVC, animated: true)
// MARK: - UIAdaptivePresentationControllerDelegate
extension ViewController: UIAdaptivePresentationControllerDelegate {
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
print("모달이 닫혔습니다")
}
}
class ModalDialogFragment : DialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_modal, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<Button>(R.id.closeButton).setOnClickListener {
dismiss()
}
}
}
자주 묻는 질문
계속하기 전에 사용자가 완료해야 하는 집중 작업(양식, 확인, 인증)에는 모달 프레젠테이션을 사용하세요. Push는 사용자가 자유롭게 뒤로 탐색할 수 있는 순차적 콘텐츠 보기에 적합합니다. 모달 창 안에는 탐색이 포함되어서는 안 됩니다.
iOS 13부터 기본 .automatic 스타일은 iPhone에서 .pageSheet를 선택합니다. .pageSheet는 대부분의 시나리오(양식, 세부 정보)에 적합합니다. .fullScreen은 미디어 콘텐츠(비디오, 사진 편집기)용입니다. .formSheet는 중앙 창이 필요한 iPad 앱용입니다.
Jetpack Compose는 간단한 모달 창용 Dialog 구성 요소와 하단 패널용 ModalBottomSheet를 제공합니다. Dialog는 onDismissRequest와 Compose 스타일의 콘텐츠를 허용합니다. 상태 보존을 위해 대화 상자 내에서 rememberSaveable을 사용하세요.
Apple HIG 및 Material Design은 중첩된 모달 창을 권장하지 않습니다. 사용자가 다른 모달 창 위에 모달 창을 열면 컨텍스트를 잃고 계층 구조에 혼란을 겪을 수 있습니다. 중첩 대신 단일 모달 창과 함께 단계 표시기 또는 마법사 패턴을 사용하세요.
iOS에서는 UIAdaptivePresentationControllerDelegate(presentationControllerShouldDismiss 메서드)를, Android에서는 OnBackPressedDispatcher를 사용하세요. 저장되지 않은 변경 사항이 있는 경우 옵션(저장, 변경 사항 취소, 화면에 유지)이 있는 AlertDialog를 표시합니다. 이는 사용자의 실수로 인한 데이터 손실을 방지합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.