I'm using the newest Xcode and Swift version.
I'm presenting a specific View Controller like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let contactViewController = storyboard.instantiateViewController(identifier: "contactViewController")
show(contactViewController, sender: self)
I'm dismissing this View Controller like this:
self.presentingViewController?.dismiss(animated: true, completion: nil)
I want to present an UIAlertController right after dismissing the View Controller.
This:
self.presentingViewController?.dismiss(animated: true, completion: nil)
let alertMessage = UIAlertController(title: "Your message was sent", message: "", preferredStyle: .alert)
let alertButton = UIAlertAction(title: "Okay", style: UIAlertAction.Style.default)
alertMessage.addAction(alertButton)
self.present(alertMessage, animated: true, completion: nil)
… of course doesn't work because I cannot present an UIAlertController on a dismissed View Controller.
What's the best way to present this UIAlertController after the View Controller is dismissed?
You can do it in completion handler by getting top controller like this
self.presentingViewController?.dismiss(animated: true, completion: {
let alertMessage = UIAlertController(title: "Your message was sent", message: "", preferredStyle: .alert)
let alertButton = UIAlertAction(title: "Okay", style: UIAlertAction.Style.default)
alertMessage.addAction(alertButton)
UIApplication.getTopMostViewController()?.present(alertMessage, animated: true, completion: nil)
})
Using this extension
extension UIApplication {
class func getTopMostViewController() -> UIViewController? {
let keyWindow = UIApplication.shared.windows.filter {$0.isKeyWindow}.first
if var topController = keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
return topController
} else {
return nil
}
}
}
Use Jawad Ali's extension, we could anchor the current presented ViewController.
And if you want to dismiss that alert later, you could do it in another completion handler as below code showed. In my case, I save a song to one playlist and dismiss this playlist and show a short time alert to let user know that saving ok.
DispatchQueue.main.async {
self?.removeSpinner()
self?.dismiss(animated: true, completion: {
let alert = UIAlertController(title: "Save to playlist", message: nil, preferredStyle: .alert)
UIApplication.getTopMostViewController()?.present(alert, animated: true, completion: {
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { _ in
alert.dismiss(animated: true)
}
})
})
}
I am trying dismiss a UIViewController that is currently presenting a UIAlertController as follows,
class SampleViewController: UIViewController {
private var alertController: UIAlertController?
override func viewDidLoad() {
super.viewDidLoad()
presentAlert()
}
fucn presentAlert() {
let alertController = UIAlertController(title: "alert", message: nil, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: .destructive, handler: {
dismiss(animated: true)
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true, completion: {
self.alertController = alertController
})
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// alertController?.dismiss(animated: true)
/*This dismisses SampleViewController when user taps Cancel
but I want user to stay on this screen when they tap Cancel*/
super.dismiss(animated: flag, completion: completion)
}
}
class currentViewController: UIViewController {
private let sampleViewController = SampleViewController()
func presentSampleViewController() {
present(sampleViewController, animated: true)
}
func dismissSampleViewController() {
sampleViewController.dismiss(animated: true)
}
}
But only the alert gets dismissed not the entire SampleViewController also I couldn't find if override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) is called from CurrentViewController or UIAlertController action.
I would like to dismiss both alert and SampleViewController together and take the user back to CurrentViewController. Any help would be highly appreciated, thanks in advance.
Call dismiss(animated: flag, completion: completion) in dismissSampleViewController() instead of sampleViewController.dismiss(animated: true)
By dismissing CurrentViewController you'll dismiss all of it's children view controllers
I'm trying to create uialertcontroller in Config.swift file as follow.
static func showAlertMessage(titleStr:String, messageStr:String) -> Void {
let window : UIWindow?
let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.Alert);
self.window!.presentViewController(alert, animated: true, completion: nil)
}
problem is I've found problem in self.window!.
Type 'Config' has no member 'window'
Please let me know how to solve that issue.
swift
Here's what I used, this is the same as #penatheboss answered, just add the ability of adding actions and handlers.
extension UIViewController {
func popupAlert(title: String?, message: String?, actionTitles:[String?], actions:[((UIAlertAction) -> Void)?]) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: .default, handler: actions[index])
alert.addAction(action)
}
self.present(alert, animated: true, completion: nil)
}
}
Just make sure actionTitles and actions array the same count. Pass nil if you don't need any action handler closure.
self.popupAlert(title: "Title", message: " Oops, xxxx ", actionTitles: ["Option1","Option2","Option3"], actions:[{action1 in
},{action2 in
}, nil])
Objective C:
Add the category for UIViewController
UIViewController+PopAlert.h
#import <UIKit/UIKit.h>
#interface UIViewController (UIViewControllerCategory)
- (void) popAlertWithTitle: (NSString*) title message: (NSString*) message actionTitles:(NSArray *) actionTitles actions:(NSArray*)actions;
#end
UIViewController+PopAlert.m
#import "UIViewController+PopAlert.h"
#implementation UIViewController (UIViewControllerCategory)
- (void) popAlertWithTitle: (NSString*) title message: (NSString*) message actionTitles:(NSArray *) actionTitles actions:(NSArray*)actions {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[actionTitles enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
UIAlertAction *action = [UIAlertAction actionWithTitle:obj style:UIAlertActionStyleDefault handler:actions[idx]];
[alert addAction:action];
}];
[self presentViewController:alert animated:YES completion:nil];
}
#end
Usage:
#import UIViewController+PopAlert.h
...
[super viewDidDisappear:animated];
NSArray *actions = #[^(){NSLog(#"I am action1");}, ^(){NSLog(#"I am action2");}];
[self popAlertWithTitle:#"I am title" message:#"good" actionTitles:#[#"good1", #"good2"] actions:actions];
self.window would mean that there's a window object in this class, and it's not the case.
You would need to use your let window : UIWindow? with window?.presentViewController(alert, animated: true, completion: nil), but this won't help, since this window does not actually represent any existing window, and it's not a view controller anyway.
So I suggest you pass the actual view controller you'll be using to the method:
static func showAlertMessage(vc: UIViewController, titleStr:String, messageStr:String) -> Void {
let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.Alert);
vc.presentViewController(alert, animated: true, completion: nil)
}
and you call it from a class where a UIViewController object is available.
Details
Swift 5.1, Xcode 11.3.1
Global UIAlertController With UIViewController Extension
extension UIViewController{
// Global Alert
// Define Your number of buttons, styles and completion
public func openAlert(title: String,
message: String,
alertStyle:UIAlertController.Style,
actionTitles:[String],
actionStyles:[UIAlertAction.Style],
actions: [((UIAlertAction) -> Void)]){
let alertController = UIAlertController(title: title, message: message, preferredStyle: alertStyle)
for(index, indexTitle) in actionTitles.enumerated(){
let action = UIAlertAction(title: indexTitle, style: actionStyles[index], handler: actions[index])
alertController.addAction(action)
}
self.present(alertController, animated: true)
}
}
Usage
Alert
self.openAlert(title: "alert",
message: "add your message",
alertStyle: .alert,
actionTitles: ["Okay", "Cancel"],
actionStyles: [.default, .cancel],
actions: [
{_ in
print("okay click")
},
{_ in
print("cancel click")
}
])
ActionSheet
self.openAlert(title: "actionsheet",
message: "add your message",
alertStyle: .actionSheet,
actionTitles: ["Okay", "Cancel"],
actionStyles: [.default, .cancel],
actions: [
{_ in
print("okay click")
},
{_ in
print("cancel click")
}
])
I suggest creating an extension:
extension UIViewController {
func showAlertMessage(titleStr:String, messageStr:String) {
let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alert, animated: true, completion: nil)
}
}
You can try this, please add below code in AppDelegate.swift file.
static func showAlertView(vc : UIViewController, titleString : String , messageString: String) ->()
{
let alertView = UIAlertController(title: titleString, message: messageString, preferredStyle: .alert)
let alertAction = UIAlertAction(title: "ok", style: .cancel) { (alert) in
vc.dismiss(animated: true, completion: nil)
}
alertView.addAction(alertAction)
vc.present(alertView, animated: true, completion: nil)
}
and call showAlertView method from any viewcontroller
AppDelegate.showAlertView(vc: self, titleString: "testTitle", messageString: "test msg")
I am suggest you write this code, but if you really need, try this:
static func showAlertMessage(titleStr:String, messageStr:String) -> Void {
let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.Alert);
if let viewController = UIApplication.sharedApplication().windows.first?.rootViewController as UIViewController? {
viewController.presentViewController(alert, animated: true, completion: nil)
}
}
At least it won't break down.
#Eric D better answer.
I created a alerMessage class .I can call any where in my application
//Common Alert Message Class
class AlertMessage {
internal static var alertMessageController:UIAlertController!
internal static func disPlayAlertMessage(titleMessage:String, alertMsg:String){
AlertMessage.alertMessageController = UIAlertController(title: titleMessage, message:
alertMsg, preferredStyle: UIAlertControllerStyle.Alert)
AlertMessage.alertMessageController.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default,handler: nil))
if let controller = UIApplication.sharedApplication().keyWindow?.rootViewController?.presentedViewController {
controller.presentViewController(AlertMessage.alertMessageController, animated: true, completion: nil)
}
else{
UIApplication.sharedApplication().delegate?.window!!.rootViewController?.presentViewController(AlertMessage.alertMessageController, animated: true, completion: nil)
}
return
}
}
Please refer the below GIT Example
https://github.com/amilaim/CommonAlertView
// ViewController.swift
// CommonAlertView
//
// Created by Amila Munasinghe on 4/25/17.
// Copyright © 2017 Developer Insight. All rights reserved.
//
import UIKit
class ViewController: UIViewController,AlertViewControllerDelegate {
#IBOutlet weak var AlertViewResultTextOutlet: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
#IBAction func ShowAlertAction(_ sender: Any) {
let alert = AlertViewController.sharedInstance
alert.delegate = self
alert.SubmitAlertView(viewController: self,title: "Developer Insight", message: "Please enter any text value")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func SubmitAlertViewResult(textValue : String) {
AlertViewResultTextOutlet.text = textValue
}
}
Common UIAlertViewController Implementation
import UIKit
protocol AlertViewControllerDelegate {
func SubmitAlertViewResult(textValue : String)
}
class AlertViewController {
static let sharedInstance = AlertViewController()
private init(){}
var delegate : AlertViewControllerDelegate?
func SubmitAlertView(viewController : UIViewController,title : String, message : String){
let alert = UIAlertController(title: title,
message: message,
preferredStyle: .alert)
// Submit button
let submitAction = UIAlertAction(title: "Submit", style: .default, handler: { (action) -> Void in
// Get 1st TextField's text
let textField = alert.textFields![0]
if(textField.text != "")
{
self.delegate?.SubmitAlertViewResult(textValue: textField.text!)
}
})
// Cancel button
let cancel = UIAlertAction(title: "Cancel", style: .destructive, handler: { (action) -> Void in })
// Add 1 textField and cutomize it
alert.addTextField { (textField: UITextField) in
textField.keyboardAppearance = .dark
textField.keyboardType = .default
textField.autocorrectionType = .default
textField.placeholder = "enter any text value"
textField.clearButtonMode = .whileEditing
}
// Add action buttons and present the Alert
alert.addAction(submitAction)
alert.addAction(cancel)
viewController.present(alert, animated: true, completion: nil)
}
}
What I'm using based on the solution by #William Hu:
func popup(caller:UIViewController, style:UIAlertControllerStyle? = UIAlertControllerStyle.alert,
title:String, message:String, buttonTexts:[String], buttonStyles:([UIAlertActionStyle?])? = nil,
handlers:[((UIAlertAction) -> Void)?], animated:Bool? = nil, completion: (() -> Void)? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: style!)
for i in 0..<buttonTexts.count {
alert.addAction(UIAlertAction(title: buttonTexts[i],
style: (buttonStyles == nil || i >= buttonStyles!.count || buttonStyles![i] == nil ?
UIAlertActionStyle.default : buttonStyles![i]!),
handler: (i >= handlers.count || handlers[i] == nil ? nil : handlers[i]!)))
}
caller.present(alert, animated: animated != nil ? animated! : true, completion: completion)
}
Single function gives Alert by default and can optionally be used for ActionSheet.
Arrays buttonTexts, buttonStyles and handlers can be of unequal sizes as per requirement.
Actions can be styled.
Animated can be specified.
Optional block can be specified to be executed when presentation finishes.
Usage:
popup(caller: self, style: UIAlertControllerStyle.alert,
title: "Title", message: "Message",
buttonTexts: ["Destructive", "Cancel", "OK"],
buttonStyles: [UIAlertActionStyle.destructive, UIAlertActionStyle.cancel],
handlers: [nil], animated: false)
You can use my Utility class created for Show Alert in Swift4. Its super easy to use just by writing single line of code:
Show Simple Alert
#IBAction func showDefaultAlert(_ sender: Any) {
Alert.showAlert(title:"Alert", message:"Default Alert")
}
Demo code link: https://github.com/smindia1988/EasyAlertInSwift4
If you want to present from AppDelegate windows you can use like this
UIApplication.sharedApplication().delegate?.window.rootViewController?.presentViewController(vc, animated: true, completion: nil)
This is also the way you can present on top of the View Controller available.
UIApplication.topViewController()?.present(alertViewController!, animated: true, completion: nil)
Im using replay kit to record my screen and when the preview screen pops up theres a cancel button to dismiss the screen but it doesn't do anything. I have the delegate func previewControllerDidFinish with the code to dismiss it but it doesn't go away. Does anyone know how to dismiss the window when pressing cancel? Thanks!
func previewControllerDidFinish(previewController: RPPreviewViewController) {
print("Preview finish")
// close preview window
previewController.dismissViewControllerAnimated(true, completion: nil)
}
In swift 3.2
First:
class ViewController: UIViewController, RPPreviewViewControllerDelegate {
func previewControllerDidFinish(_ previewController: RPPreviewViewController) {
previewController.dismiss(animated: true, completion: nil)
}
}
Then when the stop recording button called, and set delegate in closure
if RPScreenRecorder.shared().isRecording {
RPScreenRecorder.shared().stopRecording { (previewController: RPPreviewViewController?, error: Error?) in
if previewController != nil {
let alertController = UIAlertController(title: "Recoring", message: "Do you wish to discard or view your recording?", preferredStyle: .alert)
let discardAction = UIAlertAction(title: "Discard", style: .destructive, handler: nil)
let viewAction = UIAlertAction(title: "View", style: .default, handler: { (action: UIAlertAction) in
// set delegate here
previewController?.previewControllerDelegate = self
self.present(previewController!, animated: true, completion: nil)
})
alertController.addAction(discardAction)
alertController.addAction(viewAction)
self.present(alertController, animated: true, completion: nil)
}
}
}
Try to implement this delegate
- (void)previewController:(RPPreviewViewController *)previewController didFinishWithActivityTypes:(nonnull NSSet<NSString *> *)activityTypes {
[previewController dismissViewControllerAnimated:YES completion:^{
}];
}
I have trouble to display my UIAlertController because I'm trying to show it in a Class which is not an ViewController.
I already tried adding it:
var alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .Alert)
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)
Which is not working...
I didn't find any solution that worked for me yet.
I wrote this extension over UIAlertController to bring back show().
It uses recursion to find the current top view controller:
extension UIAlertController {
func show() {
present(animated: true, completion: nil)
}
func present(animated: Bool, completion: (() -> Void)?) {
if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
presentFromController(controller: rootVC, animated: animated, completion: completion)
}
}
private func presentFromController(controller: UIViewController, animated: Bool, completion: (() -> Void)?) {
if
let navVC = controller as? UINavigationController,
let visibleVC = navVC.visibleViewController
{
presentFromController(controller: visibleVC, animated: animated, completion: completion)
} else if
let tabVC = controller as? UITabBarController,
let selectedVC = tabVC.selectedViewController
{
presentFromController(controller: selectedVC, animated: animated, completion: completion)
} else if let presented = controller.presentedViewController {
presentFromController(controller: presented, animated: animated, completion: completion)
} else {
controller.present(self, animated: animated, completion: completion);
}
}
}
Now it's as easy as:
var alertController = UIAlertController(title: "Title", message: "Message", preferredStyle: .Alert)
alertController.show()
This should work.
UIApplication.sharedApplication().windows[0].rootViewController?.presentViewController(...)
Create a helper function that you call from the current view controller and pass the current view controller as a parameter:
func showAlertInVC(
viewController: UIViewController,
title: String,
message: String)
{
//Code to create an alert controller and display it in viewController
}
If you solution is not working it probably because of there is no window at that moment. I had the same problem when I was trying to show alert view in application:DidFinishLoadingWithOptions method. In this case my solution was to check if root view controller is available, and if it's not, then add notification for UIApplicationDidBecomeActiveNotification
NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationDidBecomeActiveNotification,
object: nil,
queue: NSOperationQueue.mainQueue()) {
(_) in
//show your alert by using root view controller
//remove self from observing
}
}