Swift: UIAlertController select subset from list - ios

I have a button that causes a UIAlertController to appear. I want to show a couple choices to the user and allow them to select a subset of the options.
[A +]
[B ]
[C +]
[D ]
[----]
[DONE]
I need to give an array to fill the options and then let the users select their subset (+ symbols in this example -> likely more common to use check marks). When they click done then it would let the code know their selection. Here is my current code from the button segue.
#IBAction func insideButton(sender: AnyObject) {
var refreshAlert = UIAlertController(title: "Refresh", message: "All data will be lost.", preferredStyle: UIAlertControllerStyle.Alert)
refreshAlert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: { (action: UIAlertAction!) in
print("Handle Ok logic here")
}))
refreshAlert.addAction(UIAlertAction(title: "Cancel", style: .Default, handler: { (action: UIAlertAction!) in
print("Handle Cancel Logic here")
}))
presentViewController(refreshAlert, animated: true, completion: nil)
}
How would I do this?

From the UIAlertController docs: "The UIAlertController class is intended to be used as-is and does not support subclassing. The view hierarchy for this class is private and must not be modified."
You'll have to make your own custom alert and present it.

Related

How to prevent SegmentedControl index from changing, based upon confirmation dialog box?

I have a SegmentedControl. When the user clicks on it, a confirmation dialog box appears, asking if they wish to change the value. If they click "Cancel", I want to cancel the change to the SegmentedControl value.
This is a code segment I have:
#IBAction func indexChanged(_ sender: UISegmentedControl) {
let refreshAlert = UIAlertController(title: "Update", message: "Sure you wanna change this?", preferredStyle: UIAlertControllerStyle.alert)
refreshAlert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action: UIAlertAction!) in
}))
refreshAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action: UIAlertAction!) in
// Nothing
}))
present(refreshAlert, animated: true, completion: nil)
}
Thanks in advance.
The best way is to keep a variable that holds the last selected index. On completion handler of cancel , set your segment's selected index to the variable's value. On Ok's completion handler update the variable with the current selected index.

UIAlertController Gets Deallocated Before Presenting It

I want to make a "rate app" alert, but for some reason it gets deallocated before showing it.
Here's the code:
func showAlert() {
if #available(iOS 8.0, *)
{
let alertController = UIAlertController(title: "Rate App", message: "Rate this app now", preferredStyle: .Alert)
let neverAction = UIAlertAction(title: "Never Show This Again", style: .Destructive, handler: { (action: UIAlertAction) in
self.userDefaults.setBool(true, forKey: "rateAlertRejected")
})
let rateAction = UIAlertAction(title: "Rate Now", style: .Default, handler: { (action: UIAlertAction) in
// Rate App
})
let remindAction = UIAlertAction(title: "Remind Me Later", style: .Cancel, handler: nil)
alertController.addAction(rateAction)
alertController.addAction(neverAction)
alertController.addAction(remindAction)
presentViewController(alertController, animated: true, completion: nil)
}
else
{
// Identical code (using UIAlertView) for iOS 7 which works perfectly
}
}
The method executes (in certain conditions, but for testing purposes it does it every time) after a custom unwind segue.
Why do I have this problem? I used UIAlertController before but I had no issues.
According to your comment you show showAlert in
a method that executes after a custom unwind segue.
unwind segue dismisses the view heriarchy and therefore your alert
does not get reference to a view controller to show from.
To solve this, show your alert in the View controller you unwind to or wait for the alert controller action to be completed before unwinding.

NSNotification: Attempt to present UIAlertController on ViewController whose view is not in the window hierarchy

I'm trying to show a UIAlertController in my ViewController in a function that's been called via an NSNotification. However I'm getting the error:
Attempt to present <UIAlertController: 0x7fe013d05d40> on <submarine.ViewController: 0x7fe011f20370> whose view is not in the window hierarchy!
The NSNotification is posted from a completion block (callback I guess) from something else in my UI. Because it's a callback it's failing to display. Hence I thought I'd try NSNotificationCentre to get around the problem without using the rootViewController to display the alert.
My code is:
override func viewDidAppear(animated: Bool) {
// Handle onboarding
if needsOnboarding() {
handleOnboarding() // This create the completion block that posts the NSNotification
}
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showTermsAlert:", name:"showTermsAlert", object: nil)
}
func showTermsAlert(notification: NSNotification) {
let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)
termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
UIApplication.sharedApplication().openURL(NSURL(string: "my_terms_url")!)
}))
termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
self.onboardingFinished()
}))
self.presentViewController(termsAlert, animated: true, completion: nil)
}
Has anyone got an idea why this is happening? I don't see why it's not in the window hierarchy - it's being presented from the self viewController and is created in a top-level function inside the VC.
Thanks!
EDIT: original code inside the handleOnboarding():
Library used: Onboard
func handleOnboarding() {
let secondPage = OnboardingContentViewController(title: "What's going on?", body: "Submarine routes your data through our network, around any filters and restrictions, giving you unrestricted and unmonitored internet access.", image: UIImage(named: "back"), buttonText: "Next") { () -> Void in
// do something here when users press the button, like ask for location services permissions, register for push notifications, connect to social media, or finish the onboarding process
}
secondPage.movesToNextViewController = true
let thirdPage = OnboardingContentViewController(title: "Terms of Use", body: "You must agree to our Terms of Use to use Submarine.\nIf you don't, please close Submarine.", image: UIImage(named: "back"), buttonText: "View Terms") { () -> Void in
let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)
termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
UIApplication.sharedApplication().openURL(NSURL(string: "my_policy_url")!)
}))
termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
self.onboardingFinished()
}))
self.presentViewController(termsAlert, animated: true, completion: nil)
// NSNotificationCenter.defaultCenter().postNotificationName("showTermsAlert", object: nil)
}
// Image
let onboardingVC = OnboardingViewController(backgroundImage: UIImage(named: "back"), contents: [secondPage, thirdPage])
self.navigationController?.presentViewController(onboardingVC, animated: false, completion: nil)
}
This happen when the presenting view controller is no longer part of the controller hierarchy, and it's view is no longer in the view hierarchy of any window. Most likely, the controller was dismissed or popped, but it heard the notification and attempted to present the alert controller.
You should manage your controller states more carefully. Perhaps remove observer when the controller is dismissed or popped from your controller hierarchy.
There are a few things i'd change in your code. Add a call to super in viewDidAppear:, and stop using the NSNotifications for your presentation. You don't know what thread showTermsAlert will get called on with this pattern. You can make your intent more explicit by calling showTermsAlert directly, and this will also guarantee you're on the main thread.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Handle onboarding
if needsOnboarding() {
self.showTermsAlert()
}
}
func showTermsAlert() {
let termsAlert:UIAlertController = UIAlertController(title: "Terms And Conditions", message: "Please view the terms below before accepting them.", preferredStyle: UIAlertControllerStyle.Alert)
termsAlert.addAction(UIAlertAction(title: "View Terms", style: .Default, handler: { (action: UIAlertAction!) in
UIApplication.sharedApplication().openURL(NSURL(string: "my_terms_url")!)
}))
termsAlert.addAction(UIAlertAction(title: "I Agree to the Terms", style: .Default, handler: { (action: UIAlertAction!) in
self.onboardingFinished()
}))
self.presentViewController(termsAlert, animated: true, completion: nil)
}

Possible to create UIAlertController using Apple's recommended UX guidelines?

Apple's iOS UX guidelines show the following example:
...where:
When the most likely button performs a nondestructive action, it
should be on the right in a two-button alert. The button that cancels
this action should be on the left.
When the most likely button performs a destructive action, it should
be on the left in a two-button alert. The button that cancels this
action should be on the right.
Source: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/Alerts.html
However, it does not seem possible to implement this guidance with the current iOS APIs (testing on iOS 9 with Swift). Here's one attempt. But I've tried varying the order of adding the alert actions, and changing which action is .Default vs. .Cancel style. No combination appears to work.
#IBAction func showAlertAction(sender: AnyObject)
{
let title = "An Alert that Offers Two Alternatives Is Easy for People to Use"
let alertViewController = UIAlertController(title: title, message: nil, preferredStyle: UIAlertControllerStyle.Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: { (alert :UIAlertAction!) -> Void in
print("'Cancel' tapped")
})
alertViewController.addAction(cancelAction)
let safeChoiceAction = UIAlertAction(title: "Safe Choice", style: UIAlertActionStyle.Default, handler: { (alert :UIAlertAction!) -> Void in
print("'Safe Choice' tapped")
})
alertViewController.addAction(safeChoiceAction)
self.presentViewController(alertViewController, animated: true, completion: nil)
}
Result:

Where to find a clear explanation about swift alert (UIAlertController)?

Couldn't find a clear and informative explanation for this.
After searching a while on a subject I didn't
find a clear explanation , even in it's class reference
UIAlertController Reference
It is ok, but not clear enough for me.
So after collecting some peaces I decided to make my own explanation
(Hope it helps)
So here it goes:
UIAlertView is deprecated as pointed out :
UIAlertView in Swift
UIAlertController should be used in iOS8+
so to create one first we need to instantiate it,
the Constructor(init) gets 3 parameters:
2.1 title:String -> big-bold text to display on the top of alert's dialog box
2.2 message:String -> smaller text (pretty much explains it's self)
2.3 prefferedStyle:UIAlertControllerStyle -> define the dialog box style, in most cases: UIAlertControllerStyle.Alert
Now to actually show it to the user, we can use showViewController or presentViewController and pass our alert as parameter
To add some interaction with a user we can use:
4.1
UIAlertController.addAction to create buttons
4.2
UIAlertController.addTextField to create text fields
Edit note: code examples below, updated for swift 3 syntax
Example 1: Simple Dialog
#IBAction func alert1(sender: UIButton) {
//simple alert dialog
let alert=UIAlertController(title: "Alert 1", message: "One has won", preferredStyle: UIAlertControllerStyle.alert);
//show it
show(alert, sender: self);
}
Example 2: Dialog with one input textField & two buttons
#IBAction func alert2(sender: UIButton) {
//Dialog with one input textField & two buttons
let alert=UIAlertController(title: "Alert 2", message: "Two will win too", preferredStyle: UIAlertControllerStyle.alert);
//default input textField (no configuration...)
alert.addTextField(configurationHandler: nil);
//no event handler (just close dialog box)
alert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.cancel, handler: nil));
//event handler with closure
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: {(action:UIAlertAction) in
let fields = alert.textFields!;
print("Yes we can: "+fields[0].text!);
}));
present(alert, animated: true, completion: nil);
}
Example 3: One customized input textField & one button
#IBAction func alert3(sender: UIButton) {
// one input & one button
let alert=UIAlertController(title: "Alert 3", message: "Three will set me free", preferredStyle: UIAlertControllerStyle.alert);
//configured input textField
var field:UITextField?;// operator ? because it's been initialized later
alert.addTextField(configurationHandler:{(input:UITextField)in
input.placeholder="I am displayed, when there is no value ;-)";
input.clearButtonMode=UITextFieldViewMode.whileEditing;
field=input;//assign to outside variable(for later reference)
});
//alert3 yesHandler -> defined in the same scope with alert, and passed as event handler later
func yesHandler(actionTarget: UIAlertAction){
print("YES -> !!");
//print text from 'field' which refer to relevant input now
print(field!.text!);//operator ! because it's Optional here
}
//event handler with predefined function
alert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.default, handler: yesHandler));
present(alert, animated: true, completion: nil);
}
Hope It helps, and good luck ;-)
An instance of the UIAlertController can be presented modally on screen just as any other UIViewController using the presentViewController:animated:completion: method. What makes the UIAlertController instance differentiate between working as an ActionSheet or as an AlertView is the style parameter you pass when creating it.
No more delegation
If you have used a UIActionSheet or UIAlertView, you know that the way to get a callback from it is for a class (in most cases the ViewController) to implement the UIActionSheetDelegate or UIAlertViewDelegate protocol. There are some open source projects that replaced this delegation pattern with block based callbacks, but the official APIs were never updated. UIAlertController does not use delegation. Instead, it has a collection of UIAlertAction items, that use closures (or blocks if you are using Objective-C) to handle user input.
For Action Sheet
#IBAction func showActionSheet(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Action Sheet", message: "Swiftly Now! Choose an option!", preferredStyle: .ActionSheet)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Just dismiss the action sheet
}
actionSheetController.addAction(cancelAction)
//Create and add first option action
let takePictureAction: UIAlertAction = UIAlertAction(title: "Take Picture", style: .Default) { action -> Void in
//Code for launching the camera goes here
}
actionSheetController.addAction(takePictureAction)
//Create and add a second option action
let choosePictureAction: UIAlertAction = UIAlertAction(title: "Choose From Camera Roll", style: .Default) { action -> Void in
//Code for picking from camera roll goes here
}
actionSheetController.addAction(choosePictureAction)
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
For AlertView with Text Field
#IBAction func showAlert(sender: AnyObject) {
//Create the AlertController
let actionSheetController: UIAlertController = UIAlertController(title: "Alert", message: "Swiftly Now! Choose an option!", preferredStyle: .Alert)
//Create and add the Cancel action
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//Do some stuff
}
actionSheetController.addAction(cancelAction)
//Create and an option action
let nextAction: UIAlertAction = UIAlertAction(title: "Next", style: .Default) { action -> Void in
//Do some other stuff
}
actionSheetController.addAction(nextAction)
//Add a text field
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
//TextField configuration
textField.textColor = UIColor.blueColor()
}
//Present the AlertController
self.presentViewController(actionSheetController, animated: true, completion: nil)
}
Some of the syntax has changed since the original responses. Here is some sample code that alerts the user if they are not signed in to iCloud.
CKContainer.default().accountStatus { (accountStatus, error) in
switch accountStatus {
case .available:
print("iCloud Available")
case .noAccount:
print("No iCloud account")
//simple alert dialog
let alert=UIAlertController(title: "Sign in to iCloud", message: "This application requires iClound. Sign in to your iCloud account to write records. On the Home screen, launch Settings, tap iCloud, and enter your Apple ID. Turn iCloud Drive on. If you don't have an iCloud account, tap Create a new Apple ID", preferredStyle: UIAlertControllerStyle.alert);
//no event handler (just close dialog box)
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: nil));
//show it
self.present(alert, animated: true, completion: nil)
case .restricted:
print("iCloud restricted")
case .couldNotDetermine:
print("Unable to determine iCloud status")
}
}

Resources