How to Show Different Alerts Based on a Condition After Clicking a Button in Swiftui

how to show different alerts based on a condition after clicking a button in swiftui

It is possible to do. Though you don't need to track as many states as you are.

Firstly, you only need to track if they have failed or not. So your failedRegister will track if the user has successfully registered or not. That means we can get remove the successfulRegister.

We need a variable to track whether an alert is showing or not, for this we will use the variable showAlert

As you have a linked list that provides the userinfo, we will mock that with just an array containing a couple of usernames.

So here is a simplified version of your code that should work.

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



Update using Identifiable

There is an alternative way to show different Alerts on the same View. This is to use a binding to an object that is Identifiable.

If we look at the ways we can initialise an Alert on a View we see there are two ways. The first has the following signature:

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

Which is what is used in the example above.

However there is a second way which has the following signature:

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

This second way can allow for more complex alerts to be managed. To utilise this we need something to track the state of the alerts. We can create a simple struct that conforms to Identifiable and contains an enum of the different choices that we have for an alert.

We then create an @State variable to track the AlertIdentifier and initialise to nil so that its state is empty and will not show any alerts until it is changed.

We can then add our .alert(item:content:) to our View.

Here is a simple example showing it in action.

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

Notice that in the buttons we set the showAlert to be an instance of the struct AlertIdentifier with the type of alert we want to show. In this case we have two types: success and failure (but we could have as many types as we want, and we don't need to use the names success and failure). When that is set, it will show the appropriate alert.

In our .alert(item:content:) we switch over the different ids so that we can make sure that the correct alert is shown for the correct choice.

This method is much easier than having multiple booleans, and it is easier to extend.

Addendum for Sheets and ActionSheets

Sheets and ActionSheets are very similar to Alerts in how they are presented. There are four ways to present Sheets.

These two require a Bool binding:

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

These two require an Identifiable binding:

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

For ActionSheets there are two ways, like Alerts.

With the Bool binding:

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

With the Identifiable binding:

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

Which binding should I use?

Binding<Bool>

If you only need to show one type of Alert, Sheet or ActionSheet then use the Bool binding, it saves you having to write some extra lines of code.

Binding<Identifiable?>

If you many different types of Alerts, Sheets or ActionSheets to show then choose the Identifiable binding as it makes it much easier to manage.



A simpler identifiable

A simpler version of the identifiable object would be to use an enum without wrapping it in a struct. In this case we need to conform to Identifiable so we need a computed property to store the id value. We also need to make sure that the enum uses a RawRepresentable so that we can get a value for the id that is unique. I would suggest using an Int or a String. In the example below I am using an Int.

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

case success, failure
}

Then in the view we could do the following:

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

How do I display a SwiftUI alert from outside of the ContentView?

If I understand your question correctly, you want to show an alert on the UI when some condition happens in your calculations.
Where the calculations take place somewhere else in your code, eg a task monitoring a sensor.

Here I present an approach, using NotificationCenter as shown in the example code. Whenever and wherever you are in your code, send a NotificationCenter.default.post... as in the example code, and the alert will popup.

class SomeClass {
static let showAlertMsg = Notification.Name("ALERT_MSG")

init() {
doCalculations() // simulate showing the alert in 2 secs
}

func doCalculations() {
//.... do calculations
// then send a message to show the alert in the Views "listening" for it
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
NotificationCenter.default.post(name: SomeClass.showAlertMsg, object: nil)
}
}
}

struct ContentView: View {
let calc = SomeClass() // for testing, does not have to be in this View
@State private var showingAlert = false

var body: some View {
Text("calculating...")
.alert("Important message", isPresented: $showingAlert) {
Button("OK", role: .cancel) { }
}
// when receiving the msg from "outside"
.onReceive(NotificationCenter.default.publisher(for: SomeClass.showAlertMsg)) { msg in
self.showingAlert = true // simply change the state of the View
}
}
}

SwiftUI Multiple alerts for same button

You can apply .alert only once to a View. Create a State which only handles the current State of the Alert and then two variables which decide if false or true was pressed. (might store that in one variable only aswell)

struct ContentView6: View {

@State var showAlert : Bool = false

@State var showTrueAlert = false
@State var showFalseAlert = false

var body: some View {
Button(action: {
let isTrue = Bool.random()
if isTrue
{
self.showTrueAlert = true
self.showAlert = true
print("True Alert")
} else {
self.showFalseAlert = true
self.showAlert = true
print("False Alert")
}
}) {

Text("Random Alert")
.font(.largeTitle)
}
.alert(isPresented: Binding<Bool>(
get: {
self.showAlert
},
set: {
self.showAlert = $0
self.showTrueAlert = false
self.showFalseAlert = false
})) {
if (showTrueAlert)
{
return Alert(title: Text("True"))
}
else
{
return Alert(title: Text("False"))
}
}
}
}

How can I have two alerts on one view in SwiftUI?

The second call to .alert(isPresented) is overriding the first. What you really want is one Binding<Bool> to denote whether the alert is presented, and some setting for which alert should be returned from the closure following .alert(isPresented). You could use a Bool for this, but I went ahead and did it with an enum, as that scales to more than two alerts.

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

How to present an Alert with SwiftUI

You can use a @State variable as the binding. Alternatively you can use a @EnvironmentObject variable that uses a BindableObject.

I think you need to call presentation on the root View to get it to work, adding it to a Stack, Group, etc. doesn't seem to work.

This snippet seems to do the trick. Note that @State variable is set to false after the alert is dismissed.

struct ContentView: View {

@State var showsAlert = false

var body: some View {
Button(action: {
self.showsAlert = true
}, label: {
Text("asdf")
}).presentation($showsAlert, alert: {
Alert(title: Text("Hello"))
})
}
}

how to show an alert view inside if-condition statement in swiftui?

You need to only set @State variables in your function and display the alert in the body of the view:

struct ContentView: View {
@State private var showsAlert = false
@State private var errorDescription: String?

var alertTitle: String {
errorDescription != nil ? "Photo Upload Error" : "Photo Uploaded correctly"
}

var alertMessage: String {
if let error = errorDescription {
return error
}
return "Photo uploaded"
}

var body: some View {
Text("view")
.alert(isPresented: $showsAlert) {
Alert(
title: Text(alertTitle),
message: Text(alertMessage),
dismissButton: .default(Text("OK"))
)
}
}

func uploadImage(image: UIImage) {
if let imageData = image.jpegData(compressionQuality: 1) {
...

storage.reference(withPath: path).putData(imageData, metadata: nil) {
_, err in
if let err = err {
print("an error has occurred - \(err.localizedDescription)")
// set @State variables
DispatchQueue.main.async {
self.errorDescription = err.localizedDescription
}
}
DispatchQueue.main.async {
self.showsAlert = true
}
}
}
}
}

Display an Alert inside a conditional with SwiftUI

I'm not sure why are you using UIKit. Here's an example of how an alert may be presented when something changes a flag. In this case, a two second timer:

import SwiftUI

class MyModel: ObservableObject {
@Published var isValid: Bool = false

init() {
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(2)) {
self.isValid = true
}
}
}

struct ContentView: View {
@ObservedObject var model: MyModel = MyModel()

var body: some View {
VStack {
Text("Some text")
Text("Some text")
Text("Some text")
Text("Some text")
}.alert(isPresented: $model.isValid, content: {
Alert(title: Text("Title"),
message: Text("Message"),
dismissButton: .default(Text("OK")) { print("do something") })
})
}
}

SwiftUI: How to show an alert after a sheet is closed?

You can use onDismiss.

Here are some examples based on when do you want to present an alert:

  1. Always close with an alert:
struct ContentView: View {
@State private var showSheet = false
@State private var showAlert = false

var body: some View {
Button("Press") {
showSheet = true
}
.sheet(isPresented: $showSheet, onDismiss: {
showAlert = true
}) {
Button("Close") {
showSheet = false
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Alert"))
}
}
}

  1. Close with an alert on button click only:
struct ContentView: View {
@State private var showSheet = false
@State private var showAlert = false
@State private var closeSheetWithAlert = false

var body: some View {
Button("Press") {
showSheet = true
closeSheetWithAlert = false
}
.sheet(isPresented: $showSheet, onDismiss: {
showAlert = closeSheetWithAlert
}) {
Button("Close") {
closeSheetWithAlert = true
showSheet = false
}
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Alert"))
}
}
}


Related Topics



Leave a reply



Submit