Reputation: 243
I'm trying to present a global alert in SwiftUI. This alert should be displayed on top of everything regardless of what it is currently displayed / presented on screen (a sheet for example).
This is my code:
@main
struct MyApp: App {
@State private var showAlert = false
var body: some Scene {
WindowGroup {
MainView()
.onReceive(NotificationCenter.default.publisher(for:NSNotification.Name.SomeNotification), perform: { _ in
showAlert = true
})
.alert(
isPresented: $showAlert,
content: {Alert(title: Text("Alert!"))}
)
}
}
}
This in some cases will not work, for example if the notification is received when a sheet is currently presented on screen. In this case the alert is not displayed and the following message is displayed on the console:
Blockquote [Presentation] Attempt to present <SwiftUI.PlatformAlertController: 0x7fbee6921400> on <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x7fbee642ac60> (from <TtGC7SwiftUI19UIHostingControllerGVS_15ModifiedContentVS_7AnyViewVS_12RootModifier_: 0x7fbee642ac60>) which is already presenting <TtGC7SwiftUI22SheetHostingControllerVS_7AnyView: 0x7fbee8405360>.
This make sense because I'm trying to present an alert on a view that is already presenting a sheet.
On UIKit I achieved this using the following class:
class GlobalAlertController: UIAlertController {
var globalPresentationWindow: UIWindow?
func presentGlobally(animated: Bool, completion: (() -> Void)?) {
globalPresentationWindow = UIWindow(frame: UIScreen.main.bounds)
globalPresentationWindow?.rootViewController = UIViewController()
globalPresentationWindow?.windowLevel = UIWindow.Level.alert + 1
globalPresentationWindow?.backgroundColor = .clear
globalPresentationWindow?.makeKeyAndVisible()
globalPresentationWindow?.rootViewController?.present(self, animated: animated, completion: completion)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
globalPresentationWindow?.isHidden = true
globalPresentationWindow = nil
}
}
This class allows me to display a global alert on top of everything in this way:
let alertController = GlobalAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Done", style: .cancel, handler: nil))
alertController.presentGlobally(animated: true, completion: nil)
Anyone know how to implement something like that in SwiftUI?
Upvotes: 6
Views: 1638
Reputation: 243
Just found that I can in fact use my old UIKit code to achieve this. The only thing that need to be changed is adding support for scenes (SwiftUI use scenes by design), like this:
class GlobalAlertController: UIAlertController {
var globalPresentationWindow: UIWindow?
func presentGlobally(animated: Bool, completion: (() -> Void)?) {
globalPresentationWindow = UIWindow(frame: UIScreen.main.bounds)
//This is needed when using scenes.
if let currentWindowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
globalPresentationWindow?.windowScene = currentWindowScene
}
globalPresentationWindow?.rootViewController = UIViewController()
globalPresentationWindow?.windowLevel = UIWindow.Level.alert + 1
globalPresentationWindow?.backgroundColor = .clear
globalPresentationWindow?.makeKeyAndVisible()
globalPresentationWindow?.rootViewController?.present(self, animated: animated, completion: completion)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
globalPresentationWindow?.isHidden = true
globalPresentationWindow = nil
}
}
Now I can just display the global alert like this:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
MainView()
.onReceive(NotificationCenter.default.publisher(for:NSNotification.Name.SomeNotification), perform: { _ in
let alertController = GlobalAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Done", style: .cancel, handler: nil))
alertController.presentGlobally(animated: true, completion: nil)
})
}
}
}
It works, although a more SwiftUI like approach would be nice.
Upvotes: 4