Closed nepysb closed 5 months ago
Thanks for your detailed report. Unfortunately, there is no way to control the system alerts (e.g. asking permissions from users) programmatically in iOS. System alerts are presented from SpringBoard so they are outside control of our application.
If you are talking about alerts that is presented in the application, then it is correct that ToastUI will overlap with those. Specifically, views that are presented by ToastUI will always be the frontmost view in the application. Therefore, in this example, alert will always stay behind the toast no matter the order of appearance. This is an expected behavior.
struct ContentView: View {
@State var presentToast = false
@State var presentAlert = false
var body: some View {
VStack {
Button {
presentToast = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
presentAlert = true
}
} label: {
Text("Show alert then toast")
}
.padding()
Button {
presentAlert = true
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
presentToast = true
}
} label: {
Text("Show toast then alert")
}
.padding()
}
.toast(isPresented: $presentToast, dismissAfter: 4.0) {
ToastView()
.toastViewStyle(.icon(content: {
ProgressView()
.frame(width: 150, height: 150)
.scaleEffect(4.0)
}))
}
.alert("Alert", isPresented: $presentAlert) {
Button("OK") {}
}
}
}
There are ways to handle this but it depends on the context. From my perspective, I think it is best to not show the toast when we know we will trigger system alerts (e.g. calling requestAccess()
) or user alerts from external libraries. If you still want to show the progress indicator when calling such APIs, here's a workaround:
struct ContentView: View {
@State var requestingAccess = false
var body: some View {
ZStack {
Button {
// shows the ProgressView
requestingAccess = true
requestAccess { result in
print(result)
// hides the ProgressView
requestingAccess = false
}
} label: {
Text("Request access")
}
.padding()
if requestingAccess {
ToastView()
.toastViewStyle(.icon {
ProgressView()
.frame(width: 150, height: 150)
.scaleEffect(4.0)
})
}
}
}
// external API that are not in SwiftUI realm
private func requestAccess(_ completion: @escaping (Bool) -> Void) {
print("requesting access")
// create a new alert
let alertViewController = UIAlertController(
title: "Request Access",
message: "ToastUI would like to authorize on your behalf",
preferredStyle: .alert
)
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
completion(true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in
completion(false)
}
alertViewController.addAction(okAction)
alertViewController.addAction(cancelAction)
// present the alert
let rootViewController = UIApplication.shared.windows.first!.rootViewController! // deprecated
rootViewController.present(alertViewController, animated: true)
}
}
Hey, thanks a lot for the comprehensive response and snippets. The suggested workaround is close to what I could use, but there is one major problem with it - it does not block the UI, so that other elements in the view can still be tapped. Since it is a progress indicator I need to prevent tapping other elements. Please see the snippet below.
struct ContentView: View {
@State var showToast = false
var body: some View {
ZStack {
VStack {
Button {
print("tapped")
} label: {
Text("tap to print")
}.padding(.bottom, 200)
Button {
showToast = true
} label: {
Text("Show toast")
}
.padding()
//this will block the UI
}/*.toast(isPresented: $showToast, content: {
ProgressView()
.frame(width: 150, height: 150)
.scaleEffect(4.0)
})*/
//this does not block the UI
if showToast {
ToastView()
.toastViewStyle(.icon {
ProgressView()
.frame(width: 150, height: 150)
.scaleEffect(4.0)
})
}
}
}
}
You can add a full screen Color
background behind the ProgressView
that blocks views behind it.
if showToast {
ToastView()
.toastViewStyle(.icon {
ProgressView()
.frame(width: 150, height: 150)
.scaleEffect(4.0)
})
.background {
Color.primary
.opacity(0.3)
.ignoresSafeArea()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
This issue has been closed due to inactivity. Please feel free to reopen it if you have any further questions.
Pre-requisites:
Feature Suggestion
If a toast is currently shown and a system alert appears (e.g. contacts permission request), add a possibility to hide the toast so they do not overlap.
Context
ViewController.swift
replace the code with the code below.Is there a way to automatically hide toast when a system alert appears ? This is obviously just a minimum working example, at the end this can be any system alert. Another thing is, sometimes alerts will come from external libs making it even harder to manage.