iOS: How to present UIAlertController in iCloud Singleton? (Swift) - ios

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

Related

Passing closure/ blocks in array as parameter iOS

I am trying to pass alert actions in array to a function that is used to simplify the UIAlertController configuration into one single line.
Am able to successfully pass button titles but not the alert actions.
Here is what i am doing.
+(void)showAlertWithTitle:(NSString*)title
message:(NSString*)alertmessage
buttonTitles:(NSArray*)buttonTitles
buttonActions:(NSArray*)buttonActions
inViewController:(UIViewController*)viewController {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:alertmessage preferredStyle:UIAlertControllerStyleAlert];
[buttonTitles enumerateObjectsUsingBlock:^(NSString* buttonTitle,NSUInteger idx,BOOL *stop){
UIAlertAction *action = [UIAlertAction actionWithTitle:buttonTitle style:UIAlertActionStyleDefault handler: [[buttonActions objectAtIndex:idx] copy]]; //blocks should always be copied to heap from stack else they will crash
[alert addAction:action];
}];
[viewController presentViewController:alert animated:YES completion:nil];
}
The above code file was written long back so its in objective c.
I have written some new files which are in swift and i am calling the above method in swift as below.
CommonManager.showAlert(withTitle: "", message: "Feedback Sent",
buttonTitles: ["Ok"], buttonActions: [ { (action: UIAlertAction) in
print("you pressed Ok alert button");
// call method whatever u need
}], in: self)
If i dont pass the closure it works fine, if passing the closure when clicking on Ok it crashes.
I also found that we need to copy a block when its passed as a collection and i did that but something is still not right which am not able to figure out. Can you tell me what i need to do here.
Thanks
The problem is that a Swift closure is a different kind of object than an Objective-C block, so trying to run it as a block crashes.
Normally if the Swift compiler sees that you are passing a closure to an Objective-C method with a block type parameter, it will convert the Swift closure to an Objective-C block, but in this case, it just sees you are putting it in an array, and not about what the method will do to it inside the array, so it doesn't do any conversion.
The only way I can figure out to get it to work is something like this:
CommonManager.showAlert(withTitle: "", message: "Feedback Sent",
buttonTitles: ["Ok"], buttonActions: [ { (action: UIAlertAction) in
print("you pressed Ok alert button");
// call method whatever u need
} as (#convention(block) (UIAlertAction) -> Void)!], in: self)
Rather than dealing with conversion weirdness, why not just make a native swift version?
Here's my version of the same functionality:
extension UIViewController {
func presentAlert(title: String, message: String, actions: [UIAlertAction] = [UIAlertAction(title: "OK", style: .cancel, handler: nil)], iPadOrigin: CGRect? = nil, style: UIAlertControllerStyle = .alert, animated: Bool = true, completion: (() -> ())? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: style)
actions.forEach(alert.addAction)
alert.popoverPresentationController?.sourceView = self.view
if let iPadOrigin = iPadOrigin {
alert.popoverPresentationController?.sourceRect = iPadOrigin
}
present(alert, animated: animated, completion: completion)
}
func presentAlert(title: String, message: String, actions: [UIAlertAction] = [UIAlertAction(title: "OK", style: .cancel, handler: nil)], iPadButtonOrigin: UIBarButtonItem? = nil, style: UIAlertControllerStyle = .alert, animated: Bool = true, completion: (() -> ())? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: style)
actions.forEach(alert.addAction)
alert.view.tintColor = Color.BlueDarker
alert.popoverPresentationController?.barButtonItem = iPadButtonOrigin
present(alert, animated: animated, completion: completion)
}
}
It also handles iPad differences and some nice defaults so you could just do viewController.presentAlert(title: "Error", message: "Something broke") if you want a simple alert on iPhone.

ViewController in other file

I'm a novice with Swift 2. I tried to create a UIAlertController in another file.
This is my code :
class my_view_error: UIViewController {
func my_error(my_title: String, my_message: String) {
let alertController = UIAlertController(title: my_title, message: my_message, preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in }
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true) {}
}
}
I'm receiving a compile time error:
Warning: Attempt to present on whose view is not in the window hierarchy!
My question is, is it possible to create a ViewController in a file other than ViewController.swift?
You created a UIViewController subclass called my_view_error. The UIViewController method presentViewController expects self (a my_view_error) to be on the screen at the time the method is called.
Your my_error function is perfectly fine, but it should be moved to a view controller that is actually on the screen. There is no need to create a UIViewController just to present a UIAlertController. In fact, UIAlertController is a UIViewController, and does not need help from a custom view controller.
Just like with any view controller, the view controller that is presenting the alert must be on the screen in order to perform the presentation.
Each view controller may have its own .swift file. There is no need to create one for a UIAlertController, however.
Heres a version of the function that uses rootViewController on UIApplication to get the currently on-screen view controller, and then uses that to present your alert:
func my_error(my_title: String, my_message: String) {
let alertController = UIAlertController(title: my_title, message: my_message, preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in }
alertController.addAction(OKAction)
// Uses UIApplication.sharedApplication().keyWindow!.rootViewController! instead of self
UIApplication.sharedApplication().keyWindow!.rootViewController!.presentViewController(alertController, animated: true) {}
}

Multiple UIAlertControllers in iOS

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.

Make a class comprises of the functions like UIAlertView, UIActivityIndicator and call them back in various viewControllers

This is my current code:
import UIKit
class classViewController: UIViewController {
// The function i want to call in other view controllers..
func alertView(title: String, message: String) {
var alert:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
In the other view controller, where I've made an IBAction to perform this alertView, I have done this:
#IBAction func button(sender: AnyObject) {
classViewController().alertView("title", message: "message")
}
When I run the app, after tapping the button I get this error, but no alertView:
Warning: Attempt to present on
whose view is not in the
window hierarchy!
Right. If you want to make a global class that displays alerts, you need to pass in a reference to the current view controller, and use that instead of "self" in calls like presentViewController.
Your class should probably not be a subclass of UIViewController, since it looks like you're never displaying it to the screen.
I created a Utils class that is a subclass of NSObject.
It has a method showAlertOnVC that looks like this:
class func showAlertOnVC(targetVC: UIViewController?, var title: String, var message: String)
{
title = NSLocalizedString(title, comment: "")
message = NSLocalizedString(message, comment: "")
if let targetVC = targetVC
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okButton = UIAlertAction(
title:"OK",
style: UIAlertActionStyle.Default,
handler:
{
(alert: UIAlertAction!) in
})
alert.addAction(okButton)
targetVC.presentViewController(alert, animated: true, completion: nil)
}
else
{
println("attempting to display alert to nil view controller.")
println("Alert title = \(title)")
println("Alert message = \(message)")
}
}

My UIAlertView won't dismiss from MasterViewController of UISplitViewController

I have the following code in my MasterViewController:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(false);
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if((appDelegate.firstTime) != nil){
self.displayFirstTimeAlert()
}
}
It dutifully displays the alert:
func displayFirstTimeAlert(){
let message = "Please go to settings and set your preferred zipcode."
let alertView = UIAlertView(title: "Welcome", message: message, delegate: nil, cancelButtonTitle: "Dismiss")
alertView.show()
}
However when I touch Dismiss nothing happens? What is going on?
I don't know why the code you have above is not working, as I tested it and it does in fact dismiss. However, you shouldn't really be using UIAlertView any longer since it is deprecated... UIAlertView is deprecated. Use UIAlertController with a preferredStyle of UIAlertControllerStyleAlert instead
So try presenting the alert this way...
func displayFirstTimeAlert(){
let message = "Please go to settings and set your preferred zipcode."
var alert = UIAlertController(title: "Welcome", message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}

Resources