【问题标题】:How can I have two alerts on one view in SwiftUI?如何在 SwiftUI 中的一个视图上显示两个警报?
【发布时间】:2020-01-23 22:35:39
【问题描述】:

我希望将两个独特的警报附加到同一个 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 时显示第二个警报。只有第二个警报在其状态为真时显示,但第一个警报什么也不做。

【问题讨论】:

  • 您永远不会将showFirstAlertshowSecondAlert 设置为false
  • @LinusGeffarth SwiftUI 在用户关闭警报时自动将它们设置为false
  • 啊,我的错。感谢您的提醒!
  • 当 SwiftUI 视图之一来自外部包并使用 .alert 显示消息时,这尤其隐蔽。 (这让我想起了 UIKit 中 UIAlertView 的问题——并试图在视图控制器层次结构中的多个级别上使用它们)。

标签: swift swiftui


【解决方案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)
    }
}

【讨论】:

    【解决方案2】:

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

    @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
    })
    

    【讨论】:

      【解决方案3】:

      如果您有更复杂的逻辑(例如,来自 1 个按钮的多个警报),这是另一种灵活的方法。您基本上可以将.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()
      

      【讨论】:

      【解决方案4】:
      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 }
      

      【讨论】:

        【解决方案5】:

        我改进了一点 Ben 的答案。 您可以使用 .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)
                }
            }
        }
        

        【讨论】:

        • 标题或消息是动态的怎么处理?
        【解决方案6】:

        这个解决方案有一个变体,它只使用一个状态变量而不是两个。它使用了另一个 .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"))
                    }
                }
            }
        }
        

        【讨论】:

          【解决方案7】:

          .alert(isPresented) 的第二次调用将覆盖第一次。您真正想要的是一个Binding&lt;Bool&gt; 来表示是否显示警报,以及应该从.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"))
                      }
                  }
              }
          }
          

          【讨论】:

          • 此设置似乎运行良好,但是,我在此类警报设置中的主按钮中放置的任何操作都不会触发。知道为什么吗?
          • @Michael 我在设置主要按钮操作或辅助操作时也没有任何问题。可能是 (1) 您的代码有单独的错误,或者 (2) 您可能需要同时指定主要和次要,尽管这只是在黑暗中的一个镜头。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-07-14
          • 2021-08-11
          相关资源
          最近更新 更多