在上一章节中,我们已经学习了如何使用 NavigationStack 为用户提供页面间的导航。 本章将探讨另一种常见的内容展示方式——模态视图(Modal View)。
在 iOS 中,模态框常用于提示用户输入或显示新内容,例如创建提醒、填写表单等。 我们还会学习如何在模态视图中加入自定义悬浮按钮(Floating Button)以及如何使用 Alert 发出系统弹窗提示。
目录
- 什么是模态视图 (Modal View)
- Card 式模态的默认外观
- 使用
.sheet(isPresented:)
展示模态视图 - 使用可选绑定
.sheet(item:)
展示模态视图 - 在模态视图中添加悬浮按钮以便关闭
- 使用 Alerts 发出警告或提示
- 全屏模态视图:
.fullScreenCover
- 总结
- 完整示例代码
什么是模态视图 (Modal View)
模态视图在 iOS 中是一种将新内容或功能覆盖在当前界面之上的展示方式, 它能阻止用户回到之前的内容,直到他们关闭该模态视图。
一般用于以下场景:
- 需要用户专注于当前任务(如填写表单或阅读重要通知)。
- 需要用户进行确认或输入才可继续(如新建日历事件、添加提醒)。
Card 式模态的默认外观
从 iOS 13 开始,系统默认使用卡片式(Card-like)呈现模态视图,而非覆盖全屏。 卡片会从屏幕底部向上滑入,并在顶部留出一部分主视图可见。 当用户完成查看或操作后,可通过下拉手势或关闭按钮来关闭卡片。
在 SwiftUI 中,我们可以使用 .sheet
修饰器来轻松实现这种卡片式模态视图。
使用 .sheet(isPresented:)
展示模态视图
基本用法
要使用 sheet(isPresented:)
,我们通常需要一个 @State
修饰的布尔值来控制模态视图是否显示。
例如:
.sheet(isPresented: $showModal) {
DetailView()
}
showModal
: 用于控制模态视图的展示 (true) 或隐藏 (false)。DetailView()
: 在模态视图中展示的具体内容。
实战演示
假设我们有一个文章列表,需要在用户点击文章时,以模态方式展示文章详情。
可以这样做:
-
在
ContentView
中添加一个布尔状态变量,和一个用于存储用户选中内容的selectedArticle
:@State var showDetailView = false
@State var selectedArticle: Article? -
在
List
的每个ArticleRow
中添加onTapGesture
,当用户点击时, 设置showDetailView = true
,并记录所选文章:.onTapGesture {
self.showDetailView = true
self.selectedArticle = article
} -
最后,使用
sheet(isPresented:)
进行模态展示:.sheet(isPresented: $showDetailView) {
if let selectedArticle = self.selectedArticle {
ArticleDetailView(article: selectedArticle)
}
}
这样,当用户点击文章行时,就会弹出一张卡片式的模态视图,显示 ArticleDetailView
。
使用可选绑定 .sheet(item:)
展示模态视图
为什么可选绑定更简洁
另一种方式是通过 .sheet(item:)
,它接受一个可选的绑定对象(如 @State var selectedArticle: Article?
)。
只要这个可选对象非空,就会弹出模态视图,空值则不展示。相比使用 Bool
值,这种写法更直接:
.sheet(item: $selectedArticle) { article in
ArticleDetailView(article: article)
}
- 当
selectedArticle
不为nil
时,系统会以模态形式展示对应的ArticleDetailView
。 - 在使用该方式时,就无需额外管理一个
showDetailView
布尔变量,简化了代码逻辑。
在模态视图中添加悬浮按钮以便关闭
iOS 13+ 的卡片模态视图支持下拉手势关闭,但对部分新用户来说不够直观。 我们可以在详情页上加入一个悬浮按钮(Floating Button)来手动关闭模态。
-
在详情视图中引入
@Environment(\.dismiss)
:@Environment(\.dismiss) var dismiss
-
在界面中使用
overlay
修饰器,让按钮始终悬浮在右 上角:.overlay(
HStack {
Spacer()
VStack {
Button {
dismiss()
} label: {
Image(systemName: "chevron.down.circle.fill")
.font(.largeTitle)
.foregroundStyle(.white)
}
.padding(.trailing, 20)
.padding(.top, 40)
Spacer()
}
}
)
这样,即使用户在浏览列表时向下滚动,按钮也会始终固定在右上角,方便随时关闭模态视图。
使用 Alerts 发出警告或提示
Alert 的基本用法
Alert 在 iOS 中是一种更具阻断性的模态,对用户的交互要求更高。
通过 SwiftUI 中的 .alert
修饰器可以实现该功能。
典型的语法如下:
.alert("Warning", isPresented: $showAlert, actions: {
Button("Confirm") {
// 确定操作
}
Button(role: .cancel) {
// 取消操作
} label: {
Text("Cancel")
}
}, message: {
Text("Are you sure?")
})
isPresented: $showAlert
: 使用布尔变量控制 Alert 的显示或隐藏。actions
: 定义 Alert 中的按钮。message
: 显示在标题下方的详细文案。
在模态视图中使用 Alert
可以在关闭按钮处触发一则询问是否关闭的 Alert。例如,当用户点击“关闭”时,弹出 Alert,
用户若选择 “Yes” 则 dismiss()
,选择 “No” 则取消关闭:
@State private var showAlert = false
Button(action: {
self.showAlert = true
}) {
Image(systemName: "chevron.down.circle.fill")
...
}
.alert("Reminder", isPresented: $showAlert, actions: {
Button("Yes") {
dismiss()
}
Button(role: .cancel) {
Text("No")
}
}, message: {
Text("Do you want to dismiss this article?")
})
全屏模态视图:.fullScreenCover
如果你想要在 iOS 13 之后的系统中恢复全屏覆盖的模态效果,
可以使用 .fullScreenCover
(iOS 14+ 引入)。它的用法与 sheet
类似,
只是呈现形式是全屏占据:
.fullScreenCover(item: $selectedArticle) { article in
ArticleDetailView(article: article)
}
当 selectedArticle
非空时,会自动加载全屏视图并覆盖整个屏幕。
总结
.sheet
与.fullScreenCover
:都能实现模态视图,差别在于.sheet
默认卡片式,.fullScreenCover
全屏覆盖。- 可选绑定与布尔绑定:二者都能控制模态视图的显示,使用可选绑定时可让代码更简洁。
- 悬浮按钮:可以通过
overlay
修饰器实现,结合@Environment(\.dismiss)
即可随时关闭模态视图。 - Alert:更强的阻断式弹窗,需要用户做出选择后才可解除;常用于警告、确认操作等场景。
在了解这套模式后,你可以灵活地在各类应用场景中使用卡片式或全屏模态,让用户以直觉的方式进行交互, 并在需要时辅以 Alert 进行必要的确认或警告提示。
完整示例代码
以下示例代码演示了一个完整的文章列表与详情的交互场景,包含了 全屏模态、悬浮按钮 以及 Alert 的使用, 便于你在实际项目中直接引用或二次开发。
import SwiftUI
struct ContentView: View {
@State private var selectedArticle: Article?
var body: some View {
NavigationStack {
List(articles) { article in
ArticleRow(article: article)
.onTapGesture {
self.selectedArticle = article
}
.listRowSeparator(.hidden)
}
.listStyle(.plain)
// 使用 fullScreenCover 让模态全屏显示
.fullScreenCover(item: $selectedArticle) { article in
ArticleDetailView(article: article)
}
.navigationTitle("Your Reading")
}
}
}
struct ArticleDetailView: View {
@Environment(\.dismiss) var dismiss
@State private var showAlert = false
var article: Article
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Image(article.image)
.resizable()
.aspectRatio(contentMode: .fit)
Group {
Text(article.title)
.font(.system(.title, design: .rounded))
.fontWeight(.black)
.lineLimit(3)
Text(article.author)
.font(.subheadline)
.foregroundColor(.secondary)
}
.padding(.bottom, 0)
.padding(.horizontal)
Text(article.content)
.font(.body)
.padding()
.lineLimit(1000)
.multilineTextAlignment(.leading)
}
}
// 悬浮关闭按钮
.overlay(
HStack {
Spacer()
VStack {
Button(action: {
self.showAlert = true
}) {
Image(systemName: "chevron.down.circle.fill")
.font(.largeTitle)
.foregroundStyle(.white)
}
.padding(.trailing, 20)
.padding(.top, 40)
Spacer()
}
}
)
// Alert 提示:用户可选择是否关闭模态
.alert("Reminder", isPresented: $showAlert, actions: {
Button("Yes") {
dismiss()
}
Button(role: .cancel) {
Text("No")
}
}, message: {
Text("Do you want to dismiss this article?")
})
// 让图片内容可以延伸到安全区域外
.ignoresSafeArea(.all, edges: .top)
}
}
struct ArticleRow: View {
var article: Article
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Image(article.image)
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(5)
Text(article.title)
.font(.system(.title, design: .rounded))
.fontWeight(.black)
.lineLimit(3)
.padding(.bottom, 0)
Text("By \(article.author)".uppercased())
.font(.subheadline)
.foregroundColor(.secondary)
.padding(.bottom, 0)
HStack(spacing: 3) {
ForEach(1...(article.rating), id: \.self) { _ in
Image(systemName: "star.fill")
.font(.caption)
.foregroundColor(.yellow)
}
}
Text(article.excerpt)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
// 示例数据和 Preview
struct Article: Identifiable {
var id = UUID()
var title: String
var author: String
var rating: Int
var excerpt: String
var image: String
var content: String
}
let articles = [
Article(title: "SwiftUI Basics", author: "John Doe", rating: 5,
excerpt: "A quick introduction to SwiftUI...",
image: "swiftui",
content: "Full article content goes here..."),
// 可以自行添加更多文章...
]
#Preview {
ContentView()
}
至此,你已经掌握了 模态视图(Modal View) 的多种实现方式以及 Alert 的用法, 能够在 SwiftUI 项目中为用户提供更丰富的交互体验。祝开发顺利!