How to consecutively present two alert views using SwiftUI
It appears (tested with Xcode 11.2):
- While not documented, but it is not allowed to add more than one
.alert modifier in one view builder sequence - works only latest - It is not allowed to add .alert modifier to EmptyView, it does not work
at all
I've found alternate solution to proposed by @Rohit. In some situations, many alerts, this might result in simpler code.
struct TestTwoAlerts: View {
@State var alertIsVisible = false
@State var bonusAlertIsVisible = false
var score = 100
var title = "First alert"
var body: some View {
VStack {
Button(action: {
self.alertIsVisible = true
}) {
Text("Hit Me!")
}
.alert(isPresented: $alertIsVisible) {
Alert(title: Text("\(title)"), message: Text("\n"), dismissButton:.default(Text("Next Round"), action: {
if self.score == 100 {
DispatchQueue.main.async { // !! This part important !!
self.bonusAlertIsVisible = true
}
}
}))
}
Text("")
.alert(isPresented: $bonusAlertIsVisible) {
Alert(title: Text("Bonus"), message: Text("You've earned 100 points bonus!!"), dismissButton: .default(Text("Close")))
}
}
}
}
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 show two alerts in swift ui one followed by the other
You cannot make two .alert calls directly behind each other without overwriting one of them. The key is to get all the logic inside a single .alert call.
import SwiftUI
enum ActiveAlert {
case first, second, third
}
struct ContentView: View {
@State private var showingAlert = false
@State private var activeAlert: ActiveAlert = .first
var body: some View {
Button(action: {
self.showAlert(.first)
}, label: {
Text("button")
.alert(isPresented: $showingAlert) {
switch activeAlert {
case .first:
return Alert(title: Text("First Alert"), dismissButton: .default(Text("Next"), action: {
self.showAlert(.second)
}))
case .second:
return Alert(title: Text("Second Alert"), dismissButton: .default(Text("Next"), action: {
self.showAlert(.third)
}))
case .third:
return Alert(title: Text("Third Alert"), dismissButton: .default(Text("Ok"), action: {
//...
}))
}
}
})
}
func showAlert(_ active: ActiveAlert) -> Void {
DispatchQueue.global().async {
self.activeAlert = active
self.showingAlert = true
}
}
}
Multiple Alerts in one view can not be called SwiftUI
So i'm still not 100% sure why this was unable to post an alert this way? But I was able to fix this issue simply by placing the alert in the main view.
I changed the @State
variable isResetting
to a binding bool and paced a new state variable in the main class. I then just copied over the .alert
and this seems to solve the issue.
The struct now looks like this
private struct resetButton: View {
@Binding var isResetting: Bool
var body: some View {
Button("Reset"){
self.isResetting = true
}
}
}
and the alert is the same but is now in the class calling resetButton()
.
Edit:
So it appears that SwiftUI won't let you call multiple alerts from the same view, however, if you want multiple alerts in the same view then there is a way around this.
You can call an alert from anywhere inside of a view so the following solution works.
private struct exampleAlert: View {
@State var alert1 = false
@State var alert2 = false
var body: some View {
Vstack{
Button("alert 1"){
self.alert1 = true
}.alert(isPresented: $alert1) {
Alert(title: Text("Important message"), message: Text("This alert can show"), dismissButton: .default(Text("Got it!")))
}
Button("alert 2"){
self.alert2 = true
}.alert(isPresented: $alert2) {
Alert(title: Text("Important message"), message: Text("This alert can also show"), dismissButton: .default(Text("Got it!")))
}
}
}
}
The catch is here that if either of these alerts were to be placed on the Vstack, one will not function. If they are placed on separate views though then both can be called as expected.
Perhaps this is something that will be rectified in a future update? In the meantime though, here is a solution for working around this problem.
Multiple lined UIAlertController message
It will work with \n
as edited.
let alert = UIAlertController(title: "Title",
message: "message: \n message",
preferredStyle: UIAlertControllerStyle.alert)
let cancelAction = UIAlertAction(title: "OK",
style: .cancel, handler: nil)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
Swift UI - Using UIVIewControllerRepresentable to update logs
You haven't provided a Minimal Reproducible Example but here is a simplified version of what seems to be what you are trying to do.
First, add a LogManager
that can be created by ANY class
or struct
struct LogManager{
var name: String
///Simplified post that takes in the String and uses the name as the source
func postMessage(message: String){
postMessage(message: .init(timestamp: Date(), message: message, source: name))
}
//MARK: Notification
///Sends a Notification with the provided message
func postMessage(message: Message){
NotificationCenter.default.post(name: .logManagerMessage, object: message)
}
///Adds an observer to the manager's notification
func observeMessageNotification(observer: Any, selector: Selector){
NotificationCenter.default.addObserver(observer, selector: selector, name: .logManagerMessage, object: nil)
}
}
Put at class
or struct
level the declaration for the manager
private let log = LogManager(name: "YourClassStructName")
Then call
log.postMessage(message: "your message here")
When you want to log a message.
In the ViewModel you would
- subscribe to the notifications
- maintain the array
Like below
class AppLogSampleViewModel: ObservableObject{
static let shared: AppLogSampleViewModel = .init()
private let manager = LogManager(name: "AppLogSampleViewModel")
@Published var messages: [Message] = []
private init(){
//Observe the manager's notification
manager.observeMessageNotification(observer: self, selector: #selector(postMessage(notification:)))
}
///Puts the messages received into the array
@objc
func postMessage(notification: Notification){
if notification.object is Message{
messages.append(notification.object as! Message)
}else{
messages.append(.init(timestamp: Date(), message: "Notification received did not have message", source: "AppLogSampleViewModel :: \(#function)"))
}
}
}
If your View
won't be at the highest level you need to call.
let startListening: AppLogSampleViewModel = .shared
In the ContentView
or AppDelegate
so the ViewModel
starts listening as quickly as possible. You won't necessarily use it for anything but it needs to be called as soon as you want it to start logging messages.
Only the View
that shows the messages uses the instance of the ViewModel
.
struct AppLogSampleView: View {
@StateObject var vm: AppLogSampleViewModel = .shared
//Create this variable anywhere in your app
private let log = LogManager(name: "AppLogSampleView")
var body: some View {
List{
Button("post", action: {
//Post like this anywhere in your app
log.postMessage(message: "random from button")
})
DisclosureGroup("log messages"){
ForEach(vm.messages, content: { message in
VStack{
Text(message.timestamp.description)
Text(message.message)
Text(message.source)
}
})
}
}
}
}
Here is the rest of the code you need to get this sample working.
struct AppLogSampleView_Previews: PreviewProvider {
static var previews: some View {
AppLogSampleView()
}
}
extension Notification.Name {
static let logManagerMessage = Notification.Name("logManagerMessage")
}
struct Message: Identifiable{
let id: UUID = .init()
var timestamp: Date
var message: String
var source: String
}
Only the one View
that shows the messages needs access to the ViewModel
and only one ViewModel
subscribes to the notifications.
All the other classes and structs will just create an instance of the manager and call the method to post the messages.
There is no sharing/passing between the classes and structs everybody gets their own instance.
Your manager can have as many or as little methods as you want, mine usually mimics the Logger
from osLog
with log
, info
, debug
and error
methods.
The specific methods call the osLog
and 3rd party Analytics
Services
corresponding methods.
Also, the error
method sends a notification that a ViewModel
at the top level receives and shows an Alert
.
To get all this detail working it takes a lot more code but you have the pattern with the code above.
In your code, in the updateUIViewController
you break the single source if truth rule by copying the messages and putting them in another source of truth, right here.
fullLogMessage = logViewModel.getTheFullLogMessage()
This is also done without a check to make sure that you don't go into an infinite loop. Anytime there is code in an update
method you should check that the work actually needs to be done. Such as comparing that the new location doesn't already match the old location.
Related Topics
Different Font Size for Different Devices in Xcode 6
Div Scrolling Freezes Sometimes If I Use -Webkit-Overflow-Scrolling
Get Random Child from Firebase Database
Disable Autolayout Constraint Error Messages in Debug Console Output in Xcode
Build Not Visible in Itunes Connect
Disable Swipe Back Gesture in Swift
iOS 11 Uitabbar Uitabbaritem Positioning Issue
Extra Padding Above Table View Headers in iOS 15
How to Print Core Data Debug Values
Method Load() Defines Objective-C Class Method 'Load', Which Is Not Permitted by Swift 1.2
How to Consecutively Present Two Alert Views Using Swiftui
Wkwebview Fails to Load Images and CSS Using Loadhtmlstring(_, Baseurl:)
Getting Back a Date from a String
Notificationcenter Issue on Swift 3
Find an Item and Change Value in Custom Object Array - Swift