如何在SwiftUI中点击按钮后根据条件显示不同的警报。

5

在发布之前,我进行了一些研究,但是我无法解决它。

在注册视图中,我想让用户注册。

我创建了一个链表,当用户注册用户名时,我的程序会检查该用户名是否已经被使用。

如果已被使用,当用户单击注册按钮时,应该弹出警报提示该用户名已被使用。

如果用户名未被使用,则应该显示一个警报,提示注册成功。

import SwiftUI

struct registerScreen: View {

    @State var username: String = ""
    @State var password: String = ""
    @State private var sucessfulRegister = false
    @State private var failedRegister = false

    var body: some View {

        VStack {

            TextField()
            SecureField()
            
            Button(action: {

                let userinfo = linkedList()

                if (userinfo.contains(value: self.username)){
                    // self.failedRegister = true
                    self.failedRegister.toggle()
                    // show alert that it failed

                } else {
                    userinfo.insert(value: user(username: self.username, password: self.password))
                    // show alert that it is successfull
                    self.sucessfulRegister.toggle()

                }
            })

            {

                Text("Register")
                    .font(.headline)
                    .foregroundColor(.white)
                    .padding()
                    .frame(width: 220, height: 60)
                    .background(Color.green)
                    .cornerRadius(15.0)

            }

        }

    }

}
3个回答

10

这是可能的。尽管您不需要跟踪与您一样多的状态。

首先,您只需跟踪它们是否失败。因此,您的failedRegister将跟踪用户是否成功注册。这意味着我们可以删除successfulRegister

我们需要一个变量来跟踪是否正在显示警报,为此,我们将使用变量showAlert

由于您有提供userinfo的链接列表,因此我们将使用仅包含一些用户名的数组进行模拟。

这是您的代码的简化版本,应该可以正常工作。

struct ContentView: View {
    
    var names: [String] = ["John", "Mike"]
    
    @State var username: String = ""
    @State var password : String = ""
    @State private var failedRegister = false
    
    // this value is used for tracking whether the alert should be shown
    @State private var showAlert = false
    
    var body: some View {
        VStack {
            TextField("Enter username", text: $username)

            Button(action: {
                // reset to false as this is the initial state
                self.failedRegister = false
                
                if (self.names.contains(self.username)){
                    self.failedRegister.toggle()
                } else {
                    // insert the value into the user info
                }
                self.showAlert.toggle()

            }) {
                Text("Register")
                    .font(.headline)
                    .foregroundColor(.white)
                    .padding()
                    .frame(width: 220, height: 60)
                    .background(Color.green)
                    .cornerRadius(15.0)
            }
            
        }.alert(isPresented: $showAlert) {
            // it would be nice to set failedRegister back to false in this function but you cannot modify state here.
            if self.failedRegister {
                return  Alert(title: Text("Failed to register"), message: Text("Unfortunately that username is taken"), dismissButton: .default(Text("OK")))
            } else {
                return  Alert(title: Text("Welcome"), message: Text("You have registered"), dismissButton: .default(Text("OK")))
            }
        }
    }
}


使用Identifiable进行更新

有一种在同一个View上显示不同Alerts的替代方法,那就是使用绑定到具有Identifiable属性的对象。

如果我们看一下在View上初始化Alert的方式,我们可以看到有两种方式。第一种的签名如下:

.alert(isPresented: Binding<Bool>, content: () -> Alert)

这是上面示例中使用的方法。

然而,还有一种另外的方式,其签名如下:

.alert(item: Binding<Identifiable?>, content: (Identifiable) -> Alert)

第二种方式可以管理更复杂的警报。要使用它,我们需要跟踪警报状态的内容。我们可以创建一个简单的结构体来符合 Identifiable 并包含警报的不同选择的枚举。

然后我们创建一个 @State 变量来跟踪 AlertIdentifier ,并初始化为 nil,这样它的状态就为空,并且在更改之前不会显示任何警报。

然后我们可以在 View 中添加我们的 .alert(item:content:)

以下是一个简单的示例,展示了它的实际运用。

struct ContentView:View {

    private struct AlertIdentifier: Identifiable {
        var id: Choice

        enum Choice {
            case success
            case failure
        }
    }

    @State private var showAlert: AlertIdentifier? // init this as nil

    var body: some View {
        VStack(spacing: 20) {
            Button(action: {
                self.showAlert = AlertIdentifier(id: .success)

            }, label: {
                Text("Show success alert")
            })

            Button(action: {
                self.showAlert = AlertIdentifier(id: .failure)

            }, label: {
                Text("Show failure alert")
            })
        }
        .alert(item: $showAlert) { alert -> Alert in

            switch alert.id {
            case .success:
                return Alert(title: Text("Success"), message: Text("You have successfully registered"), dismissButton: .default(Text("OK")))

            case .failure:
               return Alert(title: Text("Failure"), message: Text("You have failed to register"), dismissButton: .default(Text("OK")))
            }
        }
    }
}

请注意,在按钮中,我们将 showAlert 设置为 AlertIdentifier 结构体的一个实例,具有我们想要显示的警报类型。在这种情况下,我们有两种类型:成功和失败(但我们可以有任意数量的类型,并且不需要使用名称successfailure)。设置了此项后,它将显示相应的警报。

在我们的 .alert(item:content :) 中,我们切换不同的id ,以确保正确的选择显示正确的警报。

这种方法比拥有多个布尔变量更简单,而且更容易扩展。

用于 Sheets 和 ActionSheets 的补充说明

SheetsActionSheets 在它们的呈现方式上非常类似于 Alerts。有四种方法来展示 Sheets

这两个需要一个 Bool 绑定:

.sheet(isPresented: Binding<Bool>, content: () -> View)
.sheet(isPresented: Binding<Bool>, onDismiss: (() -> Void)?, content: () -> Void)

这两个需要一个 Identifiable 绑定:

.sheet(item: Binding<Identifiable?>, content: (Identifiable) -> View)
.sheet(item: Binding<Identifiable?>, onDismiss: (() -> Void)?, content: (Identifiable) -> View)

对于ActionSheets,有两种方式,就像Alerts一样。

通过Bool绑定:

.actionSheet(isPresented: Binding<Bool>, content: () -> ActionSheet)

通过 Identifiable 绑定:

.actionSheet(item: Binding<Identifiable?>, content: (Identifiable) -> ActionSheet)

我应该使用哪种绑定方式?

Binding<Bool>

如果您只需要显示一种类型的AlertSheetActionSheet,则使用Bool绑定,可以节省写额外代码的麻烦。

Binding<Identifiable?>

如果您有多种不同类型的AlertSheetActionSheet需要显示,则选择Identifiable绑定,它可以使管理变得更加容易。


一个更简单的可识别对象

一个更简单的可识别对象的版本是使用枚举而不是将其包装在结构体中。在这种情况下,我们需要符合Identifiable协议,因此我们需要一个计算属性来存储id值。我们还需要确保枚举使用RawRepresentable,以便我们可以获得唯一的id值。我建议使用Int或String。在下面的示例中,我使用了Int。

enum Choice: Int, Identifiable {
    var id: Int {
        rawValue
    }

    case success, failure
}

然后在视图中,我们可以执行以下操作:

struct ContentView:View {

    enum Choice: Int, Identifiable {
        var id: Int {
            rawValue
        } 
        case success, failure
    }

    @State private var showAlert: Choice? // init this as nil

    var body: some View {
        VStack(spacing: 20) {
            Button(action: {
                self.showAlert = .success

            }, label: {
                Text("Show success alert")
            })

            Button(action: {
                self.showAlert = .failure

            }, label: {
                Text("Show failure alert")
            })
        }
        .alert(item: $showAlert) { alert -> Alert in

            switch alert {
            case .success:
                return Alert(title: Text("Success"), message: Text("You have successfully registered"), dismissButton: .default(Text("OK")))

            case .failure:
               return Alert(title: Text("Failure"), message: Text("You have failed to register"), dismissButton: .default(Text("OK")))
            }
        }
    }
}

非常感谢,通过您的代码我看到了其他错误,并且现在它已经运行得很好了。 - vksdbvksbvk
顺便提一下,这个限制也适用于 macOS 中的工作表。 - Peter Schorn
@andrew 如果苹果允许对isPresented进行复杂的评估,例如.alert(isPresented: $failedRegister || $sucessfulRegister),那就太好了。 - Learn2Code
@Learn2Code 还有一种管理多个警报的方法,那就是使用 Binding<Identifiable?> 而不是 Binding<Bool>。我已经在我的原始答案中添加了一个示例,展示了如何使用它。希望你觉得有用。 - Andrew

1

虽然Andrew的回答非常详细,但以下是一个简短的答案,适用于iOS14

struct YourView: View {
    enum AlertType: Identifiable {
        case first, second
        
        var id: Int {
            hashValue
        }
    }
    
    @State var alertType: AlertType?
    
    var body: some View {
        VStack {
            Button("Show alert #1") {
                alertType = .first
            }
            
            Button("Show alert #2") {
                alertType = .second
            }
        }
        .alert(item: $alertType) { type in
            switch type {
            case .first:
                return Alert(title: Text("First alert"))
            case .second:
                return Alert(title: Text("Second alert"))
            }
        }
    }
}

1
与Andrew的解决方案相同,但是枚举超出了ContentView的范围,这允许在其他视图中使用,并集中在一个地方。
enum Choice {
    case success
    case failure
}

extension Choice: Identifiable {
    var id: Choice { self }
}

struct ContentView:View {
    .../...
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接