Modal Presentation — 在当前上下文之上显示屏幕的方式,阻止与先前界面的交互。在移动开发中,模态窗口用于聚焦任务:数据输入、确认操作、身份验证和选项选择。根据 Apple HIG, 2025,模态呈现不应超过应用中导航场景的 20%。在 Android 中,模态性通过 DialogFragment、BottomSheet 和带有特定启动标志的 Activity 实现。
要点
Modal Presentation — 是一种导航模式,新屏幕出现在当前屏幕之上,暂时阻止与父内容的交互。用户必须显式完成模态任务(取消、保存、关闭)才能返回之前的状态。
模态性解决了一个认知任务:它将用户的注意力集中在一个操作上,而不会被界面其余部分分散注意力。这对于注册表单、确认对话框、文件选择以及通过第三方服务进行身份验证至关重要。Apple 的 Human Interface Guidelines 建议仅将模态性用于在继续工作之前需要完成的任务。
与 Web 模态窗口不同,移动 Modal Presentation 可以是全屏的(占据整个屏幕)或部分的(Page Sheet、Bottom Sheet)。类型的选择取决于任务的上下文和平台约定。iOS 倾向于为大多数场景使用 Page Sheet,将 Full Screen 保留给视频播放器和照片编辑器。
Push Presentation(堆栈导航)将屏幕添加到导航堆栈并自动显示返回按钮。用户可以随时返回上一个屏幕。而 Modal Presentation 则需要显式完成:返回按钮要么不存在,要么关闭模态窗口,而不是返回到上一个屏幕。
模态呈现和 Push 呈现之间的主要区别:Modal Presentation 阻止向后导航而不丢失数据,需要操作来关闭(Save、Cancel、Done),通常代表一个单独的任务。Push Presentation 保留导航层次结构,自动添加返回按钮,适用于顺序浏览内容。
| 特征 | Modal Presentation | Push Presentation |
|---|---|---|
| 阻止返回 | 是,需要显式操作 | 否,返回按钮始终可用 |
| 典型用途 | 表单、身份验证、选择 | 详细查看、导航 |
| 动画 | 从下到上(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 中模态窗口的基类。它管理对话框的生命周期、处理屏幕旋转并保存状态。Fragment 显示在 Activity 之上,而不会阻塞导航堆栈。关闭通过 dismiss() 或单击对话框外部区域(如果 setCancelable(true))完成。
BottomSheetDialogFragment 将模态窗口显示为从底部升起的面板。这种模式在 Material Design 中用于选项选择、分享和快速操作。BottomSheet 可以具有固定高度或可扩展(peek height + full height)。在 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()
}
}
}
常见问题
对于用户需要在继续之前完成的聚焦任务使用 Modal Presentation:表单、确认、身份验证。Push 适用于顺序浏览内容,用户可以自由返回。模态窗口内部不应包含导航。
从 iOS 13 开始,默认样式 .automatic 为 iPhone 选择 .pageSheet。.pageSheet 适用于大多数场景(表单、详情)。.fullScreen — 用于媒体内容(视频、照片编辑器)。.formSheet — 用于需要居中窗口的 iPad 应用。
Jetpack Compose 提供 Dialog 组件用于简单模态窗口,ModalBottomSheet 用于底部面板。Dialog 接受 onDismissRequest 和 Compose 风格的内容。使用 rememberSaveable 在对话框内保存状态。
Apple HIG 和 Material Design 不推荐嵌套模态窗口。如果用户在另一个模态窗口上打开模态窗口,会丢失上下文并可能在层次结构中混淆。与其嵌套,不如使用 Step Indicator 或带单个模态窗口的 Wizard 模式。
使用 iOS 中的 UIAdaptivePresentationControllerDelegate(presentationControllerShouldDismiss 方法)或 Android 中的 OnBackPressedDispatcher。如果存在未保存的数据,显示一个 AlertDialog,提供选项:保存、取消更改、留在当前屏幕。这可以防止用户意外丢失数据。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。