如何在 SwiftUI 中的一个视图上有两个警报?

问题描述 投票:0回答:11

我希望将两个独特的警报附加到同一个

Button
视图。当我使用下面的代码时,只有底部的警报起作用。

我在 macOS Catalina 上使用官方版本的 Xcode 11。

@State private var showFirstAlert = false
@State private var showSecondAlert = false

Button(action: {
    if Bool.random() {
        showFirstAlert = true
    } else {
        showSecondAlert = true
    }
}) {
    Text("Show random alert")
}
.alert(isPresented: $showFirstAlert) {
    // This alert never shows
    Alert(title: Text("First Alert"), message: Text("This is the first alert"))
}
.alert(isPresented: $showSecondAlert) {
    // This alert does show
    Alert(title: Text("Second Alert"), message: Text("This is the second alert"))
}

我希望当我将

showFirstAlert
设置为 true 时显示第一个警报,并且我希望当我将
showSecondAlert
设置为 true 时显示第二个警报。当其状态为 true 时,仅显示第二个警报,但第一个警报不执行任何操作。

swift swiftui
11个回答
91
投票

.alert(isPresented)
的第二次调用将覆盖第一个。您真正想要的是一个
Binding<Bool>
来表示是否显示警报,以及应从
.alert(isPresented)
之后的闭包中返回警报的一些设置。您可以使用 Bool 来实现此目的,但我继续使用枚举来完成此操作,因为它可以扩展到两个以上的警报。

enum ActiveAlert {
    case first, second
}

struct ToggleView: View {
    @State private var showAlert = false
    @State private var activeAlert: ActiveAlert = .first

    var body: some View {

        Button(action: {
            if Bool.random() {
                self.activeAlert = .first
            } else {
                self.activeAlert = .second
            }
            self.showAlert = true
        }) {
            Text("Show random alert")
        }
        .alert(isPresented: $showAlert) {
            switch activeAlert {
            case .first:
                return Alert(title: Text("First Alert"), message: Text("This is the first alert"))
            case .second:
                return Alert(title: Text("Second Alert"), message: Text("This is the second alert"))
            }
        }
    }
}

33
投票

此解决方案有一种变体,它仅使用一个状态变量而不是两个。它利用了这样一个事实:还有另一种

.alert()
形式,它采用
Identifiable
项而不是 Bool,因此可以在其中传递额外的信息:

struct AlertIdentifier: Identifiable {
    enum Choice {
        case first, second
    }

    var id: Choice
}

struct ContentView: View {
    @State private var alertIdentifier: AlertIdentifier?

    var body: some View {
        HStack {
            Button("Show First Alert") {
                self.alertIdentifier = AlertIdentifier(id: .first)
            }
            Button("Show Second Alert") {
                self.alertIdentifier = AlertIdentifier(id: .second)
            }
        }
        .alert(item: $alertIdentifier) { alert in
            switch alert.id {
            case .first:
                return Alert(title: Text("First Alert"),
                             message: Text("This is the first alert"))
            case .second:
                return Alert(title: Text("Second Alert"),
                             message: Text("This is the second alert"))
            }
        }
    }
}

9
投票

请注意,在 iOS 16 中,一个视图上有两个警报不再是问题。 本线程中许多答案中提到的

alert(isPresented:content:)
alert(item:content:)
以及
Alert
结构已被弃用。

建议简单地使用

alert(_:isPresented:actions:message:)
或其变体之一。例如:

struct ContentView: View {
    @State private var isFirstAlertPresented = false
    @State private var isSecondAlertPresented = false

    var body: some View {
        VStack {
            Button("Show first alert") {
                isFirstAlertPresented = true
            }
            Button("Show second alert") {
                isSecondAlertPresented = true
            }
        }
        .alert(
            "First alert",
            isPresented: $isFirstAlertPresented,
            actions: {
                Button("First OK") {}
            },
            message: {
                Text("First message")
            }
        )
        .alert(
            "Second alert",
            isPresented: $isSecondAlertPresented,
            actions: {
                Button("Second OK") {}
            },
            message: {
                Text("Second message")
            }
        )
    }
}

7
投票

我改进了一点本的答案。 您可以使用 .alert(item:) 代替 .alert(isPresented:):

动态显示多个警报
struct AlertItem: Identifiable {
    var id = UUID()
    var title: Text
    var message: Text?
    var dismissButton: Alert.Button?
}

struct ContentView: View {

    @State private var alertItem: AlertItem?

    var body: some View {
        VStack {
            Button("First Alert") {
                self.alertItem = AlertItem(title: Text("First Alert"), message: Text("Message"))
            }
            Button("Second Alert") {
                self.alertItem = AlertItem(title: Text("Second Alert"), message: nil, dismissButton: .cancel(Text("Some Cancel")))
            }
            Button("Third Alert") {
                self.alertItem = AlertItem(title: Text("Third Alert"))
            }
        }
        .alert(item: $alertItem) { alertItem in
            Alert(title: alertItem.title, message: alertItem.message, dismissButton: alertItem.dismissButton)
        }
    }
}

7
投票

如果您有更复杂的逻辑(例如,来自一个按钮的多个警报),这是另一种灵活的方法。您基本上可以将

.alert
附加到任何
View
并将警报逻辑与按钮分开,如下所示:

EmptyView() 对我不起作用。在 Xcode 12.4 中测试

// loading alert
Text("")
    .alert(isPresented: $showLoadingAlert, content: {
        Alert(title: Text("Logging in"))
    })
    .hidden()

// error alert
Text("")
    .alert(isPresented: $showErrorAlert, content: {
        Alert(title: Text("Wrong passcode"), message: Text("Enter again"), dismissButton: .default(Text("Confirm")))
    })
    .hidden()

2
投票
extension Alert:Identifiable{
    public var id:String { "\(self)" }
}
@State var alert:Alert?

Button(action: {
    if Bool.random() {
        alert = Alert(title: Text("Alert 1"))
    } else {
        alert = Alert(title: Text("Alert 2"))
    }
}) {
    Text("Show random alert")
}
.alert(item:$alert) { $0 }

2
投票

我想分享一个处理多个警报的很酷的策略。 我从某人关于 Hacking with Swift 的帖子中得到了关于更改文档和文档的想法(参见@bryceac 的帖子:https://www.hackingwithswift.com/forums/swiftui/exporting-multiple-file-types/13298)视图模型中文件导出器的类型。 您可以对警报执行相同的操作(至少在很多情况下)。 最简单的警报只有信息丰富的标题和消息。 如果您可能需要显示一堆警报,则可以更改视图模型中的字符串。 例如,您的实际警报代码可能如下所示

.alert(isPresented: $viewModel.showingAlert) {
    Alert(title: Text(viewModel.alertTitle), message: Text(viewModel.alertMessage), dismissButton: .default(Text("Got it.")))
}

虽然如果您需要在主视图模型之外的其他地方更新它们,这可能会导致一些有趣的字符串传递,但我发现它效果很好(并且我在向不同视图添加不同警报时遇到了困难,正如 Paul Hudson 在 Hacking 上建议的那样无论出于何种原因使用 Swift),我不喜欢说,如果可能出现许多不同的结果,您需要通知用户,则有 10 个警报。

但我认为使用枚举更好,正如 John M (https://stackoverflow.com/users/3088606/john-m) 所建议的那样。 例如:

enum AwesomeAlertType {
    case descriptiveName1
    case descriptiveName2
}

对于简单的警报,您可以使用一个函数来使用标题和消息以及具有您选择的默认值的按钮标题来构建它们:

func alert(title: String, message: String, buttonTitle: String = "Got it") -> Alert {
    Alert(title: Text(title), message: Text(message), dismissButton: .default(Text(buttonTitle)))
}

然后,您可以使用执行如下操作:

.alert(isPresented: $viewModel.showingAlert) {
    switch viewModel.alertType {
    case descriptiveName1:
        return alert(title; "My Title 1", message: "My message 1")
    case descriptiveName2:
        return alert(title; "My Title 2", message: "My message 2")
    default:
        return alert(title: "", message: "")
    }
}

这使您可以在一个块中声明警报 UI,使用枚举和绑定到可在视图模型中分配的布尔值来控制其状态,并通过使用生成基本警报的函数(有时所有警报)来保持代码简短和干燥。你需要)带有标题和消息,以及带有标题的按钮。


1
投票

与其他人发布的内容类似,这是我的方法。这提供了一些便利,但允许自定义警报。

/// A wrapper item for alerts so they can be identifiable
struct AlertItem: Identifiable {
    let id: UUID
    let alert: Alert
    
    /// Initialize this item with a custom alert
    init(id: UUID = UUID(), alert: Alert) {
        self.id = id
        self.alert = alert
    }
    
    /// Initialize this item with an error
    init(id: UUID = UUID(), title: String = "Oops", error: Error) {
        self.init(id: id, title: title, message: error.localizedDescription)
    }
    
    /// Initialize this item with a title and a message
    init(id: UUID = UUID(), title: String, message: String? = nil) {
        let messageText = message != nil ? Text(message!) : nil
        
        self.id = id
        self.alert = Alert(
            title: Text(title),
            message: messageText,
            dismissButton: .cancel()
        )
    }
    
    /// Convenience method for displaying simple messages
    static func message(_ title: String, message: String? = nil) -> Self {
        return Self.init(title: title, message: message)
    }
    
    /// Convenience method for displaying localizable errors
    static func error(_ error: Error, title: String = "Oops") -> Self {
        return Self.init(title: title, error: error)
    }
    
    /// Convenience method for displaying a custom alert
    static func alert(_ alert: Alert) -> Self {
        return Self.init(alert: alert)
    }
}

extension View {
    func alert(item: Binding<AlertItem?>) -> some View {
        return self.alert(item: item) { item in
            return item.alert
        }
    }
}

现在你可以像这样使用你的alertItem:

struct ContentView: View {
    @Binding private let alertItem: AlertItem?
    
    var body: some View {
        VStack {
            Button("Click me", action: {
                alertItem = .message("Alert title", message: "Alert message")
            })
            
            Button("Click me too", action: {
                alertItem = .message("Alert title 2", message: "Alert message 2")
            })
        }.alert(item: $alertItem)
    }
}

0
投票

对此有两种解决方案。将您的 .alert 附加到另一个视图,例如生成警报的按钮。这是最好的解决方案,但并不总是有效,具体取决于视图。另一个选项如下,与接受的答案相比,它可以显示any警报。

@State var isAlertShown = false
@State var alert: Alert? {
    didSet {
        isAlertShown = alert != nil
    }
}

YourViews {
    Button(action: {
        alert = Alert(title: Text("BACKUP"), message: Text("OVERWRITE_BACKUP_CONFIRMATION"), primaryButton: .destructive(Text("OVERWRITE")) {
            try? BackupManager.shared.performBackup()
        }, secondaryButton: .cancel())
    }, label: {
        Text("Button")
    })
}
.alert(isPresented: $isAlertShown, content: {
    guard let alert = alert else { return Alert(title: Text("")) }
        
    return alert
})

0
投票

.alert(isPresented: $invalidPhotosOrServerError) { Alert(title: Text(invalidPhotos ? "Invalid photos Uploaded" : "Error calling server"), message: Text(invalidPhotos ? "PLease upload valid photos": "Something went wrong calling server" ), dismissButton: .cancel()) }

然后,当上传无效照片时,同时分配

invalidPhotosOrServerError
invalidPhotos
。当服务器出现错误时,仅将
invalidPhotosOrServerError
分配给
true


0
投票

我不得不做这件丑陋的事:

    private func alerts(view: any View) -> some View {
    let result = view.overlay {
        ZStack {
            ZStack { }
                .alert(viewModel.deleteButtonText, isPresented: $showingConfirmDeleteItems) {
                    Button("Cancel", role: .cancel) {}
                    Button("Delete", role: .destructive) {
                        onDeleteItemsConfirmedClicked()
                    }
                } message: {
                    Text(String(format: viewModel.deleteConfirmText, selection.count > 0 ? " (\(selection.count))" : ""))
                }
            ZStack { }
                .alert(isPresented: $showCameraSettingsAlert, content: {
                    SettingsAlert.alert(title: "Camera Access", message: viewModel.cameraSettingsAlertText, primaryButtonText: "Open Settings", isPresented: $showCameraSettingsAlert)
                })
            ZStack { }
                .alert(isPresented: $showMicrophoneSettingsAlert, content: {
                    SettingsAlert.alert(title: "Microphone Access", message: viewModel.microphoneSettingsAlertText, primaryButtonText: "Open Settings", isPresented: $showMicrophoneSettingsAlert)
                })
            ZStack { }
                .alert(isPresented: $showPhotosVideosSettingsAlert, content: {
                    SettingsAlert.alert(title: "Photos Access", message: viewModel.photosVideosSettingsAlertText, primaryButtonText: "Open Settings", isPresented: $showPhotosVideosSettingsAlert)
                })
        }
    }
    return AnyView(result)
}
© www.soinside.com 2019 - 2024. All rights reserved.