A bit of clarification on closures and competition Hanlder about alerts. which is the better usage here? how, if I should, use "action" in second case? the result seems to be the same, it works, but I'd like to better understand WHY.
import UIKit
struct exapleStruct {
var inHotel = true
}
class ViewController : UIViewController {
var exapleStruct : exapleStruct!
var detailTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let myAlertController = UIAlertController(title: "Add or Change value", message: "", preferredStyle: .alert)
//firstExample
let booleanChange = UIAlertAction(title: "change", style: .default, handler: self.handlerForBool)
//second exampple how shoukld I use "action" ?? why is it there?
let booleanChange2 = UIAlertAction(title: "change", style: .default) { (action) in
print(self.exapleStruct.inHotel)
self.detailTable.reloadData()
}
myAlertController.addAction(booleanChange)
present(myAlertController, animated: true, completion: nil)
}
func handlerForBool(alertARgument: UIAlertAction!) {
print(exapleStruct.inHotel)
self.detailTable.reloadData()
}
}
Using this
let booleanChange2 = UIAlertAction.init(title: "option1", style: .default,
handler: handlerForBool(alertARgument:))
let booleanChange3 = UIAlertAction.init(title: "option2", style: .default,
handler: handlerForBool(alertARgument:))
func handlerForBool(alertARgument: UIAlertAction!) {
print(exapleStruct.inHotel)
self.detailTable.reloadData()
}
is useful when you need same action for multiple alertActions , it's function reuseability
Related
Is there any separate global function to add a different style and a different handler for alerts?
My function from AppDelegate looks like this:
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)
}
You just need to add more parameters to your function. In the code below I've added the following: controllerStyle for UIAlertController, actionStyle for UIAlertAction and action for UIAlertAction handler.
static func showAlertView(vc : UIViewController, titleString : String , messageString: String, controllerStyle: UIAlertController.Style = .alert, actionStyle: UIAlertAction.Style = .cancel, action: #escaping () -> () = {}) {
let alertView = UIAlertController(title: titleString, message: messageString, preferredStyle: controllerStyle)
let alertAction = UIAlertAction(title: "ok", style: .cancel) { (alert) in
if action == {} {
vc.dismiss(animated: true, completion: nil)
} else {
action()
}
}
alertView.addAction(alertAction)
vc.present(alertView, animated: true, completion: nil)
}
I have been trying to get the answer from a user from the UIAlertController. The problem is that the code still runs while the UIAlertController is displayed. I would like to show the alert, and then wait until the user gives an answer to continue the code.
func showPopUp(name:String)->String{
var gender = ""
let alert = UIAlertController(title: "What are you "+name+"?", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Boy", style: .default, handler: { action in
gender = "Boy"
}))
alert.addAction(UIAlertAction(title: "Girl", style: .default, handler: { action in
gender = "Girl"
}))
self.present(alert, animated: true)
return gender
}
override func viewDidLoad() {
super.viewDidLoad()
print("This should appear before the alert")
var characters: [String] = ["John", "Tom", "Martha"]
for ch in characters{
let a = showPopUp(name: ch)
print(ch + " is a "+ a)
}
}
I cannot put the code inside the action of the alert because it is inside a for loop, and therefore it continues without getting the gender.
You need to use a completion handler, since the user input happens asynchronously, so you cannot return it using a synchronous return.
Unrelated to your issue, but you should be using String interpolation rather than + to concanate Strings.
func showPopUp(name:String, genderCompletion: #escaping (String)->()) {
let alert = UIAlertController(title: "What are you \(name)?", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Boy", style: .default, handler: { action in
genderCompletion("Boy")
}))
alert.addAction(UIAlertAction(title: "Girl", style: .default, handler: { action in
genderCompletion("Girl")
}))
self.present(alert, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
let characters: [String] = ["John", "Tom", "Martha"]
for ch in characters{
showPopUp(name: ch, genderCompletion: { gender in
print("\(ch) is a \(gender)")
})
}
}
Opening the UIAlertController on button click, the action is going to open but main issue is the UIAlertAction methods are not performed on its click. Here is Code block :
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// getData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//menuBtn is the button
#IBAction func menuBtn(sender: UIButton) {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
let orders = UIAlertAction(title: "Orders", style: .Default, handler: { (alert: UIAlertAction!) -> Void in
let alertViewController = self.storyboard?.instantiateViewControllerWithIdentifier("OrdersViewController") as! OrdersViewController
self.presentViewController(alertViewController, animated: true, completion: nil)
})
let about = UIAlertAction(title: "About", style: .Default, handler: {(alert: UIAlertAction!) -> Void in
let aboutObject = self.storyboard?.instantiateViewControllerWithIdentifier("AboutViewController") as! AboutViewController
self.presentViewController(aboutObject, animated: true, completion: nil)
})
let contactUs = UIAlertAction(title: "Contact Us", style: .Default, handler: {(alert: UIAlertAction!) -> Void in
let alertViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ContactViewController") as! ContactViewController
self.presentViewController(alertViewController, animated: true, completion: nil)
})
let login = UIAlertAction(title: "LogIn", style: .Default, handler: {(alert: UIAlertAction!) -> Void in
let alertViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as! LoginViewController
self.presentViewController(alertViewController, animated: true, completion: nil)
})
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
optionMenu.addAction(orders)
optionMenu.addAction(about)
optionMenu.addAction(contactUs)
optionMenu.addAction(login)
optionMenu.addAction(cancelAction)
self.presentViewController(optionMenu, animated: true, completion: nil)
}
This is code is working fine, I have checked it's opening new viewController as well.
Cross check points:
Controller class and stroybaord are connected
Storyboard ID has been assigned
IBAction must be connected to IBOutlet
On the Button click Action you have to write code.. Try This code.
let alert = UIAlertController(title: "Saved", message: "Selected Frame is Saved", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Ok", style:.Default , handler: { (UIAlertAction) in
}))
//Add action like this
self.presentViewController(alert, animated: true, completion: nil)
Still need any help feel free to ask.
First of all check if the button action is touch up inside. Make the button action as touch up inside. Below code works for me. Hope this works for you as well. Change the action title according to your need.
#IBAction func menuBtn(sender: AnyObject) {
let actionSheet = UIAlertController()
let criticalAction = UIAlertAction(title : "CRITICAL" , style : UIAlertActionStyle.Default){
(action) in
//This section will be executed when the buttons are pressed
//Do your work here.
debugPrint("CRITICAL")
}
let highAction = UIAlertAction(title : "HIGH" , style : UIAlertActionStyle.Default){
(action) in
//This section will be executed when the buttons are pressed
//Do your work here.
debugPrint("HIGH")
}
actionSheet.addAction(criticalAction)
actionSheet.addAction(highAction)
self.presentViewController(actionSheet, animated: true, completion: nil)
}
I have this button who's calling a function(showActionSheetForPost) in another ViewController (TimelineViewController).
My Button:
weak var timeline: TimelineViewController?
#IBAction func moreButtonTapped(sender: AnyObject) {
timeline?.showActionSheetForPost(post!)
}
The other ViewController (TimelineViewController):
class TimelineViewController: UIViewController, TimelineComponentTarget {
#IBOutlet weak var tableView: UITableView!
// MARK: UIActionSheets
func showActionSheetForPost(post: Post) {
if (post.user == PFUser.currentUser()) {
showDeleteActionSheetForPost(post)
} else {
showFlagActionSheetForPost(post)
}
}
func showDeleteActionSheetForPost(post: Post) {
let alertController = UIAlertController(title: nil, message: "Do you want to delete this post?", preferredStyle: .ActionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
alertController.addAction(cancelAction)
let destroyAction = UIAlertAction(title: "Delete", style: .Destructive) { (action) in
post.deleteInBackgroundWithBlock({ (success: Bool, error: NSError?) -> Void in
if (success) {
self.timelineComponent.removeObject(post)
} else {
// restore old state
self.timelineComponent.refresh(self)
}
})
}
alertController.addAction(destroyAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
func showFlagActionSheetForPost(post: Post) {
let alertController = UIAlertController(title: nil, message: "Do you want to flag this post?", preferredStyle: .ActionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
alertController.addAction(cancelAction)
let destroyAction = UIAlertAction(title: "Flag", style: .Destructive) { (action) in
post.flagPost(PFUser.currentUser()!)
}
alertController.addAction(destroyAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
}
My problem:
when I touch the Button (moreButtonTapped) The Action Sheet doesn't appear.
Thank you very much
You need to do like:
TimelineViewController().showActionSheetForPost(post!)
or set
weak var timeline: TimelineViewController()
#iamalizade
I took a screenshot of all the errors in my TimelineViewController
The errors
I've been looking up a lot of tutorials on UIAlertController. Thus far, the way I found was to activate a UIAlertController by linking it to a button or label and then call a IBAction.
I tried to replicate the code to automatically pop an alert when user enters the app (I wanted to ask the user if they want to go through the tutorial). However, I keep getting the error:
Warning: Attempt to present UIAlertController on MainViewController whose view is not in the window hierarchy!
Then I tried to add the UIAlertController to the MainViewController via addChildViewController and addSubview. However, I get the error:
Application tried to present modally an active controller
I figured that I cannot use the presentViewController function and commented it out.
The UIAlertController is displayed BUT when I tried to click on the cancel or the never button, this error occurs.
Trying to dismiss UIAlertController with unknown presenter.
I am really stumped. Can someone share what I am doing wrong? Thank you so much. Here is the code.
func displayTutorial() {
alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
self.addChildViewController(alertController)
self.view.addSubview(alertController.view)
alertController.didMoveToParentViewController(self)
alertController.view.frame.origin.x = self.view.frame.midX
alertController.view.frame.origin.y = self.view.frame.midY
//alertController.popoverPresentationController?.sourceView = self.view*/
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Destructive) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
//self.presentViewController(alertController, animated: false) {}
}
I found the solution. Apparently, I cannot call the UIAlertController from the func viewDidLoad. I must call the function from viewDidAppear. So my code now is
override func viewDidAppear(animated: Bool) {
if tutorial == 0 {
displayTutorial(self.view)
}
}
func displayTutorial(sender:AnyObject) {
let alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Default) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
self.presentViewController(alertController, animated: true, completion: nil)
if let pop = alertController.popoverPresentationController {
let v = sender as UIView
pop.sourceView = view
pop.sourceRect = v.bounds
}
}
Thanks to this posting: Warning: Attempt to present * on * whose view is not in the window hierarchy - swift
Below UIAlertController with extension would help you show alert with dynamic number of buttons with completion handler for selected index
extension UIViewController {
func displayAlertWith(message:String) {
displayAlertWith(message: message, buttons: ["Dismiss"]) { (index) in
}
}
func displayAlertWith(message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
displayAlertWithTitleFromVC(vc: self, title: Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String, andMessage: message, buttons: buttons, completion: completion)
}
func displayAlertWithTitleFromVC(vc:UIViewController, title:String, andMessage message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
for index in 0..<buttons.count {
let action = UIAlertAction(title: buttons[index], style: .default, handler: {
(alert: UIAlertAction!) in
if(completion != nil){
completion(index)
}
})
alertController.addAction(action)
}
DispatchQueue.main.async {
vc.present(alertController, animated: true, completion: nil)
}
}
}
If you need to auto dismiss the alert you can call dismiss on presented view controller after some delay.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
vc.dismiss(animated: true, completion: nil)
}
Hope this might help you.