In my app there are scenarios where multiple alerts could come. But as in iOS8 UIAlertview turned to UIAlertController, i am not able to show multiple alerts as you can not present two or more controllers at the same time.
How can I achieve this using UIAlertController?
Here is the method to show multiple alertControllers :
UIAlertController *av = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:kAlertOk
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
}];
[av addAction:cancelAction];
UIWindow *alertWindow = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
alertWindow.rootViewController = [[UIViewController alloc]init];
alertWindow.windowLevel = UIWindowLevelAlert + 1;
[alertWindow makeKeyAndVisible];
[alertWindow.rootViewController presentViewController:av animated:YES completion:nil];
You can keep track of a list of alerts to show in your view controller as an instance variable, say
NSMutableArray *alertsToShow;
You can present the first UIAlertController, and add a UIAlertAction in which you present the next alert (if applicable), with a recursive-like method:
- (void)showAlertIfNecessary {
if (alertsToShow.count == 0)
return;
NSString *alert = alertsToShow[0];
[alertsToShow removeObjectAtIndex:0];
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"Title"
message:alert
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
[self showAlertIfNecessary];
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
Note that this can get very annoying to the user, if he/she needs to click through a lot of messages. You might consider combining them into a single message.
Swift version for #Bejibun's answer above:
let alertView = UIAlertController(title: "New Message", message: "Message Body", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { (UIAlertAction) in
alertView.dismissViewControllerAnimated(true, completion: nil)
}))
let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.presentViewController(alertView, animated: true, completion: nil)
You cannot show multiple alerts simultaneously, and if you were doing so before, you were behaving badly. Rethink your interface.
You can easily present alerts in succession, which is all you really need:
let alert = UIAlertController(title: "One", message: nil, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Next", style: .Default, handler: {
_ in
let alert2 = UIAlertController(title: "Two", message: nil, preferredStyle: .Alert)
alert2.addAction(UIAlertAction(title: "OK", style: .Cancel, handler:nil))
self.presentViewController(alert2, animated: true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
You need to present it on a top presented controller, you can use this extension:
extension UIViewController {
var topController: UIViewController {
presentedViewController?.topController ?? self
}
}
self.topController.present(alert, animated: true)
I think I am pretty late for this but still posting as It might be useful for someone looking for this though Apple doesn't recommend multiple alerts stacking thats why they deprecated this UIAlertView from to UIAlertController implementation.
I have created a AQAlertAction subclass for UIAlertAction. You can use it for staggering Alerts, the usage is same as you are using UIAlertAction. All you need to do is import AQMutiAlertFramework in your project or you can include class also (Please refer Sample project for that). Internally It uses binary semaphore for staggering the Alerts until user handle action associated with current alert displayed. Let me know if it works for you.
You can use JSAlertView which handles both UIAlertView and UIAlertController APIs.
It handles multiple alerts fired at same time, very well. It also provide super easy methods for displaying simple alerts.
You can Find Top Most View Controller using this Function.
func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
Present UIAlertController Using this Method through. like,
topViewController()?.present(alertController, animated: true, completion: nil)
Method Info: topViewController() find a top Most presented view controller,
UIAlertController supper class is UIViewController.
first UIAlertController is open normal in top presented view controller, try to open a second UIAlertController then topViewController() given first alert view. so no any UIAlertController missed.
Related
In my app there are scenarios where multiple alerts could come. But as in iOS8 UIAlertview turned to UIAlertController, i am not able to show multiple alerts as you can not present two or more controllers at the same time.
How can I achieve this using UIAlertController?
Here is the method to show multiple alertControllers :
UIAlertController *av = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:kAlertOk
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
}];
[av addAction:cancelAction];
UIWindow *alertWindow = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
alertWindow.rootViewController = [[UIViewController alloc]init];
alertWindow.windowLevel = UIWindowLevelAlert + 1;
[alertWindow makeKeyAndVisible];
[alertWindow.rootViewController presentViewController:av animated:YES completion:nil];
You can keep track of a list of alerts to show in your view controller as an instance variable, say
NSMutableArray *alertsToShow;
You can present the first UIAlertController, and add a UIAlertAction in which you present the next alert (if applicable), with a recursive-like method:
- (void)showAlertIfNecessary {
if (alertsToShow.count == 0)
return;
NSString *alert = alertsToShow[0];
[alertsToShow removeObjectAtIndex:0];
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"Title"
message:alert
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
[self showAlertIfNecessary];
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
Note that this can get very annoying to the user, if he/she needs to click through a lot of messages. You might consider combining them into a single message.
Swift version for #Bejibun's answer above:
let alertView = UIAlertController(title: "New Message", message: "Message Body", preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: { (UIAlertAction) in
alertView.dismissViewControllerAnimated(true, completion: nil)
}))
let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.presentViewController(alertView, animated: true, completion: nil)
You cannot show multiple alerts simultaneously, and if you were doing so before, you were behaving badly. Rethink your interface.
You can easily present alerts in succession, which is all you really need:
let alert = UIAlertController(title: "One", message: nil, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Next", style: .Default, handler: {
_ in
let alert2 = UIAlertController(title: "Two", message: nil, preferredStyle: .Alert)
alert2.addAction(UIAlertAction(title: "OK", style: .Cancel, handler:nil))
self.presentViewController(alert2, animated: true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
You need to present it on a top presented controller, you can use this extension:
extension UIViewController {
var topController: UIViewController {
presentedViewController?.topController ?? self
}
}
self.topController.present(alert, animated: true)
I think I am pretty late for this but still posting as It might be useful for someone looking for this though Apple doesn't recommend multiple alerts stacking thats why they deprecated this UIAlertView from to UIAlertController implementation.
I have created a AQAlertAction subclass for UIAlertAction. You can use it for staggering Alerts, the usage is same as you are using UIAlertAction. All you need to do is import AQMutiAlertFramework in your project or you can include class also (Please refer Sample project for that). Internally It uses binary semaphore for staggering the Alerts until user handle action associated with current alert displayed. Let me know if it works for you.
You can use JSAlertView which handles both UIAlertView and UIAlertController APIs.
It handles multiple alerts fired at same time, very well. It also provide super easy methods for displaying simple alerts.
You can Find Top Most View Controller using this Function.
func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
Present UIAlertController Using this Method through. like,
topViewController()?.present(alertController, animated: true, completion: nil)
Method Info: topViewController() find a top Most presented view controller,
UIAlertController supper class is UIViewController.
first UIAlertController is open normal in top presented view controller, try to open a second UIAlertController then topViewController() given first alert view. so no any UIAlertController missed.
Background & Short Summary
I am using WkWebview in order to show web pages for my app. I have it so that you can choose an image from camera or photo library. However there seems to be an issue with the app crashing on selecting the image.
Specs
I am running on IOS 10.0.2 on Tablet , and IOS 10.0 on the simulator using Swift 3. I am running both from XCode 8.
On the simulator I am getting an "error" when trying to upload images
I get the following message:
2016-10-19 02:15:36.150670 z4[31561:14708540] [Generic]
Creating an image format with an unknown type is an error
The image is fine and I am able to use it for upload. This behavior I thought was weird but I read that it has to do with memory management on IOS
On the tablet itself I get the following
Terminating app due to uncaught exception 'NSGenericException',
reason: 'Your application has presented a
UIAlertController (<UIAlertController: 0x151e80350>)
of style UIAlertControllerStyleActionSheet.
The modalPresentationStyle of a UIAlertController
with this style is UIModalPresentationPopover.
You must provide location information for this
popover through the alert controller's popoverPresentationController.
You must provide either a sourceView and sourceRect or a barButtonItem.
If this information is not known when you present the alert controller,
you may provide it in the UIPopoverPresentationControllerDelegate method
-prepareForPopoverPresentation.'
The app seems to crash in the AppDelegate. I have no idea how to do their recommendations. I also don't know if this is part of a deeper issue, or if I am missing something really simple.
Code that I have related to UIAlerts
The following are 3 functions I have related to UIAlertController
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: #escaping () -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
completionHandler()
}))
self.popoverPresentationController?.sourceView = self.view
self.popoverPresentationController?.sourceRect = self.view.bounds
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: #escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action) in
completionHandler(true)
}))
alertController.addAction(UIAlertAction(title: "No", style: .default, handler: { (action) in
completionHandler(false)
}))
self.popoverPresentationController?.sourceView = self.view
self.popoverPresentationController?.sourceRect = self.view.bounds
self.present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: #escaping (String?) -> Void) {
let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .actionSheet)
alertController.addTextField { (textField) in
textField.text = defaultText
}
alertController.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action) in
if let text = alertController.textFields?.first?.text {
completionHandler(text)
} else {
completionHandler(defaultText)
}
}))
alertController.addAction(UIAlertAction(title: "No", style: .default, handler: { (action) in
completionHandler(nil)
}))
self.popoverPresentationController?.sourceView = self.view
self.popoverPresentationController?.sourceRect = self.view.bounds
self.present(alertController, animated: true, completion: nil)
}
How can I handle this exception and fix this problem so my app doesn't crash on me? I can provide more details and code if needed. Thank you for your help in advance.
You have to do small changes in your code to work in iPad. I am adding the missing line of your code.
self.popoverPresentationController = alertController.popoverPresentationController
alertController.modalPresentationStyle = .Popover
Add these two lines of code in your 3 functions.
Please use below Objective-C code as reference. So it may be work for you.
- (void)showAlertWithTitle:(NSString *)title withMessage:(NSString *)message withStyle:(UIAlertControllerStyle) alertStyle andActions:(NSArray *)actions andSource:(UIView *)sourceView{
UIAlertController *alertController= [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:alertStyle];
for (UIAlertAction *action in actions) {
[alertController addAction:action];
}
UIWindow *alertWindow = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
alertWindow.rootViewController = [[UIViewController alloc]init];
alertWindow.windowLevel = UIWindowLevelAlert + 1;
[alertWindow makeKeyAndVisible];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[alertController setModalPresentationStyle:UIModalPresentationPopover];
UIPopoverPresentationController *popPresenter = [alertController
popoverPresentationController];
popPresenter.sourceView = sourceView;
popPresenter.sourceRect = sourceView.bounds;
}
[alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}
- (void)dismissAlertController{
UIWindow *topWindow = [UIApplication sharedApplication].windows.lastObject;
[topWindow.rootViewController dismissViewControllerAnimated:YES completion: nil];}
To use above methods, Here is the sample.
__weak typeof(self) weakSelf = self;
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
// Cancel button tappped.
[weakSelf dismissAlertController];
}];
UIAlertAction *removeAction = [UIAlertAction actionWithTitle:#"Remove" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
[weakSelf dismissAlertController];
//Do your actions
}];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
[self showAlertWithTitle:#"Are you sure you want to remove?" withMessage:nil withStyle:UIAlertControllerStyleActionSheet andActions:#[removeAction,cancelAction]];
}
else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad){
[self showAlertWithTitle:#"Are you sure you want to remove?" withMessage:nil withStyle:UIAlertControllerStyleActionSheet andActions:#[removeAction,cancelAction] andSource:sender];
}
I m trying to display a UIAlertView from the appDelegate in
didReceiveRemoteNotification
when the app receive a push notification.
I ve this error :
Warning: Attempt to present <UIAlertController: 0x14c5494c0> on <UINavigationController:
0x14c60ce00> whose view is not in the window hierarchy!
here is my code :
func application(application: UIApplication, didReceiveRemoteNotification userInfo: NSDictionary) {
var contentPush: NSDictionary = userInfo.objectForKey("aps") as NSDictionary
var message = contentPush.objectForKey("alert") as String
let alertController = UIAlertController(title: "Default Style", message: message, preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
// ...
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in
let photoPushedVc = self.storyboard.instantiateViewControllerWithIdentifier("CommentTableViewController") as CommentTableViewController
println("the fetched post is \(post)")
photoPushedVc.post = post
let activeVc = UIApplication.sharedApplication().keyWindow?.rootViewController
activeVc?.presentViewController(photoPushedVc, animated: true, completion: nil)
}
alertController.addAction(OKAction)
let activeVc = UIApplication.sharedApplication().keyWindow?.rootViewController
activeVc?.presentViewController(alertController, animated: true, completion: nil)}
To generate AlertController Dialog Box from AppDelegate using Objective-C,
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Title" message:#"Hello World!" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* ok = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:ok];
Type 1
UIWindow *alertWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
alertWindow.rootViewController = [[UIViewController alloc] init];
alertWindow.windowLevel = UIWindowLevelAlert + 1;
[alertWindow makeKeyAndVisible];
[alertWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
Type 2
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
[topController presentViewController:alertController animated:YES completion:nil];
Both are tested and working fine.
Ok i finally got it, you need to find the active VC using this before trying to present your alertController :
let navigationController = application.windows[0].rootViewController as UINavigationController
let activeViewCont = navigationController.visibleViewController
activeViewCont.presentViewController(alertController, animated: true, completion: nil)
Here is mine Swift 3.0 example
func showTopLevelAlert() {
let alertController = UIAlertController (title: "title", message: "message.", preferredStyle: .alert)
let firstAction = UIAlertAction(title: "First", style: .default, handler: nil)
alertController.addAction(firstAction)
let cancelAction = UIAlertAction(title: "Отмена", style: .cancel, handler: nil)
alertController.addAction(cancelAction)
let alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.rootViewController = UIViewController()
alertWindow.windowLevel = UIWindowLevelAlert + 1;
alertWindow.makeKeyAndVisible()
alertWindow.rootViewController?.present(alertController, animated: true, completion: nil)
}
Hope it helps to someone
If you need the same in Objective-c
UIAlertController *alertvc = [UIAlertController alertControllerWithTitle:#"Alert Title..!!" message:#"Hey! Alert body come here." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *actionOk = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}];
[alertvc addAction:actionOk];
If you have navigation based app:
UINavigationController *nvc = (UINavigationController *)[[application windows] objectAtIndex:0].rootViewController;
UIViewController *vc = nvc.visibleViewController;
[vc presentViewController:alertvc animated:YES completion:nil];
If you have single view based app:
UIViewController *vc = self.window.rootViewController;
[vc presentViewController:alertvc animated:YES completion:nil];
how i did it
func showAlertAppDelegate(title : String,message : String,buttonTitle : String,window: UIWindow){
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: buttonTitle, style: UIAlertActionStyle.Default, handler: nil))
window.rootViewController?.presentViewController(alert, animated: true, completion: nil)
}
Use Example
self.showAlertAppDelegate(title: "Alert",message: "Opened From AppDelegate",buttonTitle: "ok",window: self.window!);
Download Example With Source Code
I use an UIViewController extension to get the current visible view controller (see: How to get visible viewController from app delegate when using storyboard?).
and then present the alert controller:
let visibleVC = UIApplication.sharedApplication().keyWindow?.rootViewController?.visibleViewController
visibleVC!.presentViewController(alertController, animated: true, completion: nil)
To show alert on top controller from app delegate
var topController : UIViewController = (application.keyWindow?.rootViewController)!
while ((topController.presentedViewController) != nil) {
topController = topController.presentedViewController!
}
//showAlertInViewController func is in UIAlertController Extension
UIAlertController.showAlertInViewController(topController, withMessage: messageString, title: titleString)
Add extension in UIAlertController
static func showAlertInViewController(viewController: UIViewController?, withMessage message: String, title: String) {
let myAlert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
myAlert.addAction(UIAlertAction(title: NSLocalizedString("Ok", comment: ""), style: .Default, handler: { (action: UIAlertAction!) in
print("Handle Ok logic here")
// Let the alert simply dismiss for now
}))
viewController?.presentViewController(myAlert, animated: true, completion: nil)
}
I'm creating a Singleton to manage my iCloud setup and configuration.
As part of the iCloud Design Guide the document states:
https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html
Listing 1-4 Inviting the user to use iCloud
if (currentiCloudToken && firstLaunchWithiCloudAvailable) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle: #"Choose Storage Option"
message: #"Should documents be stored in iCloud and
available on all your devices?"
delegate: self
cancelButtonTitle: #"Local Only"
otherButtonTitles: #"Use iCloud", nil];
[alert show];
}
I've converted this to a UIAlertController in Swift but how can I run self.presentViewController(alert, animated: true, completion: nil) from my singleton?
Obviously my singleton does not have a member named 'presentViewController'
Is there anyway to do this or do I need to setup a Post Notification, present the Alert somewhere else in my program and then save/retrieve the user's selection?
1) Add custom protocol delegate to your singleton, and this delegate will return UIViewController instance
protocol TestSingletonDelegate : NSObjectProtocol {
func responderViewControllerForTestSingleton(singleton: TestSingleton)->UIViewController
}
And in your class:
let delegate: TestSingletonDelegate
func presentAlertView() {
let alertView = UIAlertView();
if (self.delegate.respondsToSelector("responderViewControllerForTestSingleton:")) {
let viewController = self.delegate.responderViewControllerForTestSingleton(TestSingleton())
viewController.presentViewController(alertView, animated: YES, completion: nil)
}
}
2) Add instance var to your singleton class, and story UIViewController variable there
I had the same issue, I solved it in my case by simply returning the alertController to the singleton and then presenting it there.
In the AlertView Class I have the below code for a simple alert that can take the title and message as input and display's only one OK button to dismiss the alert:
func alert(#alertTitle: String, alertMessage: String) -> UIAlertController {
var alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .Alert)
var okButton = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(okButton)
return alertController
}
Note that in the above method I am not presenting the alert controller to the view rather just returning it to whom ever is calling the method.
Then in the class where I called the method I am doing the below code:
** Just a note that the class where I am calling the alert is a ViewController in Objective-C. I'll provide swift code below as well**
// Creating an instance of the AlertView class and initializing it
AlertView *testAlert = [[AlertView alloc] init];
/* Creating a UIAlertController object and then calling the method I created in the AlertView class. This way the returned UIAlertController will be assigned to this UIAlertController
*/
UIAlertController *alertController = [testAlert alertWithAlertTitle:#"Genius!" alertMessage:#"Pure Genius"];
// Finally presenting the alert controller
[self presentViewController:alertController animated:true completion:nil];
NOTE: that I've found that you have to do this outside of ViewDidLoad. It doesn't work in ViewDidLoad and gives the below error:
Attempt to present <UIAlertController: 0x1555118f0> on <ViewController: 0x155509790> whose view is not in the window hierarchy!
Instead you can do this in viewDidAppear method.
Now Swift Version of ViewController:
// Created a function to show the alert
func showAlert() {
// Creating an instance of the AlertView class and initializing it
var testAlert : AlertView = AlertView()
/* Creating a UIAlertController object and then calling the method I created in the AlertView class. This way the returned UIAlertController will be assigned to this UIAlertController
*/
var alertController : UIAlertController = testAlert.alert(alertTitle: "Genius", alertMessage: "Pure Genius")
// Finally presenting the alert controller
self.presentViewController(alertController, animated: true, completion: nil)
}
One more thing if you are trying to show this alert in a Singleton outside of the ViewController then you can retrieve the top view controller as below and then present the alert controller to it:
Objective - C:
UIViewController *topViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
[topViewController presentViewController:alertController animated:true completion:nil];
Swift:
if let topViewController = UIApplication.sharedApplication().keyWindow?.rootViewController? {
topViewController.presentViewController(alertController, animated: true, completion: nil)
}
class SharedDataSingleton {
init(){
//println("Hellow World")
}
var sample = "Hellow word"
func showAlertView(title alerTitle:String ,message alertMessage:String, preferredStyle style:UIAlertControllerStyle, okLabel: String, cancelLabel: String, targetViewController: UIViewController,okHandler: ((UIAlertAction!) -> Void)!, cancelHandler: ((UIAlertAction!) -> Void)!){
let alertController = UIAlertController(title: alerTitle, message: alertMessage, preferredStyle: style)
let okAction = UIAlertAction(title: okLabel, style: .Default, handler: okHandler)
let cancelAction = UIAlertAction(title: cancelLabel, style: .Default,handler: cancelHandler)
// Add Actions
alertController.addAction(okAction)
alertController.addAction(cancelAction)
// Present Alert Controller
targetViewController.presentViewController(alertController, animated: true, completion: nil)
}
}
let sharedDataSingletonInstance = SharedDataSingleton()
> Access the values like below
sharedDataSingletonInstance.showAlertView(title: "Sample",
message: "Sample",
preferredStyle: UIAlertControllerStyle.Alert,
okLabel: "Ok",
cancelLabel: "Cancel",
targetViewController: self,
okHandler: { (action) -> Void in
println("The user is not okay.")
},
cancelHandler: { (action) -> Void in
println("The user is not okay.")
})
}
> Global Variables can be also accessed anywhere in our project
sharedDataSingletonInstance.sample
Previous to iOS8 we used the UIActionSheet for showing alert and now we need to use the UIAlertController.
When we used the UIActionSheet we could easily handle situations where the user clicked outside the pop up (which means he want to cancel the operation) by comparing the clickedButtonAtIndex to the cancelButtonIndex - if the user indeed pressed outside the popup we got the cancel button index in this function.
How can we handle these situations with the new UIAlertController? I tried to use the "completion" block but it doesn't have any context. Is there an easy way to handle this? (other than "saving" the actions states in some general variable).
You can add an action with style:UIAlertActionStyleCancel and the handler for this action is called when the user taps outside the popup.
if ([UIAlertController class]) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Alert Title" message:#"A Message" preferredStyle:UIAlertControllerStyleActionSheet];
[alertController addAction:[UIAlertAction actionWithTitle:#"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
NSLog(#"User clicked button called %# or tapped elsewhere",action.title);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSLog(#"User clicked button called %#",action.title);
}]];
[alertController addAction:[UIAlertAction actionWithTitle:#"Other" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
NSLog(#"User clicked button called %#",action.title);
}]];
UIControl *aControl = (UIControl *) sender;
CGRect frameInView = [aControl convertRect:aControl.bounds toView:self.view];
alertController.popoverPresentationController.sourceRect = frameInView;
alertController.popoverPresentationController.sourceView = self.view;
alertController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
[self presentViewController:alertController animated:YES completion:nil];
}
The solution which works for UIAlertController with alert style. Just needed to add gesture recognizer to alertController superview.
[self presentViewController: alertController
animated: YES
completion:^{
alertController.view.superview.userInteractionEnabled = YES;
[alertController.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: #selector(alertControllerBackgroundTapped)]];
}];
- (void)alertControllerBackgroundTapped
{
[self dismissViewControllerAnimated: YES
completion: nil];
}
UITapGestureRecognizer didn't work for me so I've used this way:
func addDismissControl(_ toView: UIView) {
let dismissControl = UIControl()
dismissControl.addTarget(self, action: #selector(self.dismissAlertController), for: .touchDown)
dismissControl.frame = toView.superview?.frame ?? CGRect.zero
toView.superview?.insertSubview(dismissControl, belowSubview: toView)
}
func dismissAlertController() {
self.dismiss(animated: true, completion: nil)
}
func presentAlertController(title: String?, message: String?, preferredStyle: UIAlertControllerStyle, handler: ((UIAlertAction) -> Swift.Void)? = nil, completion: (() -> Swift.Void)? = nil) {
let alertController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "OK", style: .default) { (alertAction) -> Void in
handler?(alertAction)
})
self.present(alertController, animated: true, completion: {
self.addDismissControl(alertController.view)
completion?()
})
}
func someWhereInYourViewController() {
// ...
presentAlertController(title: "SomeTitle", message: "SomeMessage", preferredStyle: .actionSheet, handler: { (alertAction) -> Void in
//do some action
}, completion: {
//do something after presentation
})
// ...
}