Alert 警告提示弹窗

Alert 是一种强制回应视图。当它出现时,整个画面会被锁住。如果你不点击上面的任何一个选项,你无法做接下来的动作。

我们要谨慎使用警报,仅在需要立即关注或做出明确决策的紧急情况下使用。标题应简短,消息应简洁,并使用清晰、以操作为导向的按钮标签,而不是模糊的响应。始终提供有意义的默认操作和安全的取消方式,并在适当的情况下为按钮分配角色,例如 destructive 或 cancel 。限制选项数量,以免让用户感到不知所措;如果信息不足以打断操作,则应选择干扰性较低的 UI 元素,例如横幅或内联消息。


参数:弹窗标题

// 弹窗标题,字符串类型
.alert("Important message", isPresented: $showingAlert) {
		Button("OK") { }
}

参数:弹窗按钮闭包

// 弹窗按钮视图的闭包
.alert("Important message", isPresented: $showingAlert) {
		Button("OK") { }
		// Button 后面如果是空的闭包,这意味着没有设置任何在按下按钮时运行的功能
		// 这不重要,因为点击 Alert 中的任何按钮都会自动关闭 Alert 弹窗,不需要自己设置
		// 如果有任何额外的功能需要添加,就加到 button 的闭包中
}

// Alert 弹窗最多可以添加 2 个按钮,不能再多
// 并且最好添加按钮的角色,以确保每个按钮的作用一目了然
.alert("Important message", isPresented: $showingAlert) {
    Button("Delete", role: .destructive) { }
    Button("Cancel", role: .cancel) { }
}

参数:消息文本 message

// 还可以在窗口内添加消息文本,以在标题旁边加上第二个尾随尾部 message,如下所示:
Button("Show Alert") {
    showingAlert = true
}
.alert("Important message", isPresented: $showingAlert) {
    Button("OK", role: .cancel) { }
} message: {
    Text("Please read this.")
}
// 两个闭包一起设置
.alert( ... )

//第1个闭包:设置按钮
{
		//除了上面一个例子的设置方式,还可以让系统提供默认的“确定”按钮,_ in代表不需要任何按钮
		_ in
}
//第2个闭包:设置说明文字
message: {
		facility in
		Text(facility.description)
}

参数:isPresented 绑定 Bool

// 使用 isPresent 双向数据绑定状态属性的布尔值。
// 绑定后,程序将监视该布尔值 ,一旦它为真就会显示弹窗;
.alert("Important message", isPresented: $showingAlert) {
		Button("OK") { }
}

// 为什么要双向绑定?因为当弹窗被关闭时,布尔值会自动设回 false

参数:presenting 绑定对象

// 1. 首先创建一个符合 Identifiable 协议的简单 User 结构
struct User: Identifiable {
    var id = "Taylor Swift"
}

// 2. 然在 ContentView 内创建一个属性来跟踪选择的用户,默认设置为 nil
@State private var selectedUser: User? = nil
@State private var isShowingUser = false

// 3. 需要同时传递【布尔值】和【可选值】,这样可以在需要时显示弹窗,也可以通过可选值进行解包
Text("Hello, World!")
		.onTapGesture{
				// 如果只保留布尔值这一行代码,也是能呈现 alert 的,只是获取不到可选值的数据了
				isShowingUser = true
				// 如果只保留可选值赋值这一句代码,无法呈现 alert
				selectedUser = User()
		}
		.alert(
				"Welcome",
				isPresented: $isShowingUser,
				presenting: selectedUser
		){
				//成功获取解包后的 user
				user in
				Button(user.id){}
		}
// 加上 Message 参数的写法,我们可以将数据呈现与自定义消息结合起来以提供更多上下文
.alert("Delete Project", isPresented: $reconfirmDialog, presenting: projectToDelete, actions: { project in
		Button("Delete", role: .destructive) { removeProject(project) }
    Button("Cancel", role: .cancel) { }
}, message: { project in
    Text("该项目\\(project.name)所包含的所有点子也将被删除。")
})

<aside> 💡 注意:用 Alert 绑定 optional 值的时候,必须同时传递: 控制视图展示的【布尔值】,以及 遵循 Identifiable 协议的 【可选值】。这点和 sheet 视图不一样,参见 参数:item 绑定 Optional

</aside>


Alert 弹窗的错误处理

SwiftUI 提供了专门的 alert 修饰符来处理错误。要使用这些修饰符,你的错误类型必须遵循 LocalizedError 协议。以下是示例:

enum MyError: LocalizedError {
    case network
    case unknown

    var errorDescription: String? {
        switch self {
        case .network: return "Network error occurred."
        case .unknown: return "Unknown error occurred."
        }
    }
    
    var recoverySuggestion: String? {
        switch self {
        case .network: return "Check your internet connection."
        case .unknown: return "Get in touch with us."
        }
    }
}

设置完后,基本错误警报自动使用错误的 localizedDescription 作为标题:

.alert(isPresented: $isPresented, error: error) {
    Button("Retry") {
        // Handle retry action
    }
    Button("Cancel") {
        // Handle cancel action
    }
}

要获得更详细的错误呈现,您可以在操作和消息闭包中访问错误对象,这种方法会根据发生的错误类型为用户提供具体的恢复建议。

.alert(isPresented: $isPresented, error: error) { error in
    Button("Retry") {
        // Handle retry based on error type
    }
    Button("Cancel") {
        // Handle cancel action
    }
} message: { error in
    Text(error.recoverySuggestion ?? "Please try again later.")
}

显示多个 Alert 弹窗的 bug

当你尝试将多个 alert() 修饰符附加到同一个视图时,可能会发现您的代码无法按预期工作(一个 alert 可以工作,但另一个则不行)。

要解决此问题,需要确保为同一个视图不要附加超过一个的 alert() 修饰符。记住:在一个页面中,其实不需要将警报附加到同一视图元素后面,您可以将它们放到任何地方。所以,你可能会发现将它们直接附加到显示它们的东西后面(例如触发按钮)最适合。


confirmationDialog 确认对话框

SwiftUI 使用 confirmationDialog() 修饰符向用户显示一系列选项。之前的 ActionSheet 在 iOS14 已被 confirmationDialog 代替。如果您的目标是 iOS 14 之前版本,则需要使用 ActionSheet 。但如果目标是 iOS 15 版本,或者想支持 macOS,则应该使用 confirmationDialog() 。

SwiftUI 提供了 alert() 来呈现重要的选择;提供了 sheet() 来在当前视图之上呈现整个视图;它也提供了 confirmationDialog() ,一种可以添加许多按钮的替代方案 。它是一个从屏幕底部向上滑动的按钮列表,您可以添加任意数量的按钮,它甚至可以滚动。

从创建方式上看,confirmationDialogAlert 几乎相同:

从视觉上看,confirmationDialogAlert 非常不同:


参数:标题&消息

.confirmationDialog("Change background", isPresented: $showingConfirmation) { ... }

// 标题显示或隐藏
.confirmationDialog("Change background", isPresented: $showingConfirmation, titleVisibility: .visible) { ... }

参数:消息闭包

.confirmationDialog("Change background", isPresented: $showingConfirmation) {
	...
} message: {
    Text("Select a new color")
}

//除了提供一个标题,还可以选择附加一条消息。按钮按照提供的顺序垂直堆叠在屏幕上,通常最好在末尾添加一个取消按钮。虽然可以通过点击屏幕上的其他位置来取消,但最好为用户提供明确的说明选项。

参数:isPresented 绑定 Bool

@State private var showingConfirmation = false

// 绑定状态属性:决定对话框当前是否显示
.confirmationDialog("Change background", isPresented: $showingConfirmation) { ... }

参数:按钮组视图的闭包

// 按钮是按照提供的顺序垂直堆叠在屏幕上,通常最好在末尾添加一个取消按钮
// 虽然可以通过点击屏幕上的其他位置来取消,但最好为用户提供明确的说明选项

.confirmationDialog("Change background", isPresented: $showingConfirmation) {
    Button("Red") { backgroundColor = .red }
    Button("Green") { backgroundColor = .green }
    Button("Blue") { backgroundColor = .blue }
    Button("Cancel", role: .cancel) { }
} message: {
    Text("Select a new color")
}

完整例子:

@State private var showingConfirmation = false

.confirmationDialog("Change background", isPresented: $showingConfirmation) {
    Button("Red") { backgroundColor = .red }
    Button("Green") { backgroundColor = .green }
    Button("Blue") { backgroundColor = .blue }
    Button("Cancel", role: .cancel) { }
} message: {
    Text("Select a new color")
}

// 因为这个新的 API 更加灵活,实际上可以使用 ForEach 将这些操作折叠成一个简单的循环:
struct ContentView: View {
    @State private var showingOptions = false
    @State private var selection = "None"

    var body: some View {
        VStack {
            Text(selection)

            Button("Confirm paint color") {
                showingOptions = true
            }
            .confirmationDialog("Select a color", isPresented: $showingOptions, titleVisibility: .visible) {
                ForEach(["Red", "Green", "Blue"], id: \\.self) { color in
                    Button(color) {
                        selection = color
                    }
                }
            }
        }
    }
}