PAYATHON 2026

SwiftUI 如何在按钮下方显示确认弹窗?

数据阿明

结论

iOS 16 没有公开的 SwiftUI 修饰符可以让 .alert() 或 .confirmationDialog() 在 iPhone 上显示成 Reminders 那种贴近按钮、带小箭头的确认气泡。要让内容出现在按钮附近,可以将 .popover 附加到按钮,并在弹层中自定义确认内容。弹层会锚定到按钮,但外观由应用控制,无法保证与 Reminders 完全一致。

为什么现有修饰符不会贴在按钮旁

.alert() 使用系统警告框样式。.confirmationDialog() 则根据设备和呈现环境显示系统提供的确认界面,在 iPhone 上通常是屏幕底部的操作表。两者都没有指定按钮锚点的参数。

UIKit 的 UIAlertController 也有类似限制:在适用的 iPad 场景中,.actionSheet 可以通过 popoverPresentationController 指定锚点;在 iPhone 上通常仍会显示为底部操作表。仅设置 UIKit 锚点,无法让 iPhone 上的界面变成 Reminders 式气泡。

使用 SwiftUI popover 锚定按钮

下面的示例适用于 iOS 16.4 及以上版本。.presentationCompactAdaptation(.popover) 请求系统在紧凑宽度环境中保留 popover 呈现。实际布局仍可能因系统版本和呈现环境而异。

struct MyView: View {
    @Environment(\.dismiss) private var dismiss
    @State private var showDiscardConfirmation = false

    var body: some View {
        NavigationStack {
            Form {
                // Content here
            }
            .navigationTitle("New Reminder")
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button {
                        showDiscardConfirmation = true
                    } label: {
                        Image(systemName: "xmark")
                            .font(.system(size: 14, weight: .semibold))
                            .foregroundColor(.secondary)
                            .frame(width: 30, height: 30)
                            .background(Color.gray.opacity(0.1))
                            .clipShape(Circle())
                    }
                    .popover(
                        isPresented: $showDiscardConfirmation,
                        attachmentAnchor: .rect(.bounds),
                        arrowEdge: .top
                    ) {
                        VStack(alignment: .leading, spacing: 12) {
                            Text("Discard this new reminder?")
                                .font(.headline)

                            HStack {
                                Button("Keep Editing") {
                                    showDiscardConfirmation = false
                                }

                                Button("Discard Changes", role: .destructive) {
                                    showDiscardConfirmation = false
                                    dismiss()
                                }
                            }
                        }
                        .padding()
                        .frame(width: 280)
                        .presentationCompactAdaptation(.popover)
                    }
                }
            }
        }
    }
}

.popover 附加在按钮上,因此按钮会作为弹层的锚点。arrowEdge: .top 请求箭头位于弹层顶部,通常会让弹层出现在按钮下方。具体位置由系统布局决定。

UIKit 方案的边界

使用 UIKit 标准操作表时,可以创建 .actionSheet 类型的 UIAlertController,并为 iPad 设置 popoverPresentationController 的 sourceView 和 sourceRect。这种做法适合需要系统操作表样式的场景,但不能保证 iPhone 上显示为按钮下方的小气泡。

如果需要严格复刻某个系统界面的外观,就要自行构建和维护 SwiftUI 弹层内容。系统版本变化也可能影响呈现细节。对于 iOS 16.0 至 16.3,还需检查目标 SDK 支持的紧凑宽度适配方式,不能假定上述适配修饰符可用。

备注:内容仅供参考。