Passing closure/ blocks in array as parameter iOS - 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.

Related

iOS - UIAlertController actions not triggering handler

I'm extremely new to iOS. I'm trying to show a dialog to the user to get some input, but the actions are never triggered. I've been searching on the net for hours and no answer seem to work for me.
Here's the function I'm trying to use to show the dialog:
private func showAmountDialog(type: String, onComplete: #escaping (Double) -> Void) {
let alert = UIAlertController(title: "Enter an amount", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: LzStrings.Common_Cancel, style: .cancel, handler: nil))
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "0.00 \(type)"
textField.keyboardType = .decimalPad
})
alert.addAction(UIAlertAction(title: LzStrings.Common_OK, style: .default) { (UIAlertAction) in
if let input = alert.textFields?.first?.text, let amount = Double(input) {
print("Your amount: \(amount)")
}
})
self.present(alert, animated: true)
}
self here is my ViewController which has a parent of UIViewController type and several other protocols.
What I might be doing wrong?
EDIT: The way I knew it isn't executing is using break-points and not by relying on print("...")
Also, since I added the TextField right before adding the action, the nullability check is useless and the textFields.first is never nil, so in both cases, a break-point should be triggered or the print("...") should be executed, which neither of them is happening.
EDIT 2: Since the if statement can do a little distraction, I edited my code this way and tested again:
alert.addAction(UIAlertAction(title: LzStrings.Common_OK, style: .default) { (UIAlertAction) in
if let input = alert.textFields?.first {
if let amount = Double(input.text ?? "") {
print("Your amount: \(amount)")
} else {
print("Can't cast this string to double")
}
} else {
print("Text field is null")
}
})
Still, no feedback from the dialog.
PS: Even the Cancel button doesn't work.
EDIT 3: My dismiss function is overridden in the super class, but it passes completion closure normally:
override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self.navigationController as? NavigationController {
navigationController.dismiss(animated: flag, completion: completion)
} else {
super.dismiss(animated: flag, completion: completion)
}
}
After having a conversation with one of my colleagues, we found out that to show standard UIAlertController we must use this:
self.view.window!.rootViewController?.present(alert, animated: true, completion: nil)
Instead of this
self.present(alert, animated: true, completion: nil)
It fixed my issue. I hope someone will find this helpful.
Another option is to use an extention for ViewController:
extension UIViewController {
//Show a basic alert
func showAlert(alertText : String, alertMessage : String) {
let alert = UIAlertController(title: alertText, message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Got it", style: UIAlertActionStyle.default, handler: nil))
//Add more actions as you see fit
self.present(alert, animated: true, completion: nil)
}
}

Passing the function of the UIAlertAction to the UIAlertController extension

I want to have a base UIAlertController and I want to use it in different classes by just passing the buttons and their closures. To achieve this, I created an extension from UIAlertController like below:
extension UIAlertController {
func showAlert(buttons: [ButtonsAction]?) -> UIAlertController {
let alert = self
guard let alertButtons = buttons else {
return alert
}
for button in alertButtons {
let alertAction = UIAlertAction(title: button.title, style: button.style, handler: {action in
button.handler()
})
alert.addAction(alertAction)
}
return alert
}
}
for my buttons I have a struct:
struct ButtonsAction {
let title: String!
let style: UIAlertAction.Style
let handler: () -> Void
}
In one of my viewControllers I have a function which shows the alert. In that function I have a title and a message then I want to have 1 button to dismiss the alert. The function is something like this:
func fetchFaild(title: String, message: String) {
let buttons = ButtonsAction.init(title: "cancel", style: .cancel, handler: {action in
//here I want to dissmiss the alert I dont know how
})
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert).showAlert(buttons: buttons)
alert.show(self, sender: nil)
}
I have problem adding buttons to the Alert and I don't know how to add actions to the buttons.
I know this is not the best practice here. If any one knows any example or any tutorial that can help me achieve this I really appreciate it.
An extension of UIViewController might be a more reasonable solution and the ButtonsAction struct seems to be redundant.
extension UIViewController {
func showAlert(title: String, message: String, actions: [UIAlertAction], completion: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach{alertController.addAction($0)}
self.present(alertController, animated: true, completion: completion)
}
}
class MyController : UIViewController {
func fetchFailed(title: String, message: String) {
let actions = [UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
print("Cancel tapped")
})]
showAlert(title: title, message: message, actions: actions)
}
}

How to add a function as a parameter in a custom function - swift?

I am trying to refactor my UIAlertViewController and pass a function to be executed when a user chooses to tap on one of two options which triggers an action.
My question is how do I go about adding a function as a parameter to a custom function? My effort is below. It is incomplete but any guidance would be much appreciated. I would like to have the function 'performNetworkTasl' as a parameter to 'showBasicAlert'.
import Foundation
import UIKit
struct Alerts {
static func showBasicAlert(on vc: UIViewController, with title: String, message: String, function: ?????){
let alert = UIAlertController.init(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction.init(title: "OK", style: .default) { (UIActionAlert) in
performNetworkTasl()
vc.dismiss(animated: true, completion: nil)
}
alert.addAction(okAction)
}
}
func performNetworkTasl(){
// DO SOME NETWORK TASK
}
You wouldnt pass a function as such, rather would pass a closure as a argument. Functions in swift are special cases of closure. Closure can be assumed to be a anonymous function. Closures,instant methods and static methods they all vary only in their context capturing ability other than their obvious syntactic differences.
struct Alerts {
static func showBasicAlert(on vc: UIViewController, with title: String, message: String, okAction: #escaping (() -> ())){
let alert = UIAlertController.init(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction.init(title: "OK", style: .default) { (UIActionAlert) in
okAction()
//dismiss statement below is unnecessary
vc.dismiss(animated: true, completion: nil)
}
alert.addAction(okAction)
}
}
And you call the function as
Alerts.showBasicAlert(on: your_viewController, with: "abcd", message: "abcd", okAction: {
//do whatever you wanna do here
})
Hope this helps
BTW, you dont have to have a explicit vc.dismiss(animated: true, completion: nil) as a last statement in any action, once the action is triggered, UIAlertController is dismissed by default

ELCImagePickerController delegate methods not called

I'm trying to build an app using ELCImagePickerController. I found that I could select multiple pictures. However, the ELCImagePickerController delegate method was not called.
This is my code:
#IBAction func uploadImages(sender: AnyObject) {
// Create the alert controller
//var alertController = UIAlertController(title: "", message: "", preferredStyle: .Alert)
var alertController = UIAlertController(title: nil, message: nil, preferredStyle: .Alert)
// Create the actions
var takeAction = UIAlertAction(title: "Take Photos", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("Take Photos Pressed")
}
var selectAction = UIAlertAction(title: "Select Photos", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("Select photos Pressed")
var imagePicker = ELCImagePickerController(imagePicker: ())
imagePicker.maximumImagesCount = 2
imagePicker.returnsOriginalImage = false
imagePicker.returnsImage = true
imagePicker.onOrder = true
imagePicker.delegate = self
self.presentViewController(imagePicker, animated: true, completion: nil)
}
var cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel) {
UIAlertAction in
NSLog("Cancel Pressed")
}
// Add the actions
alertController.addAction(takeAction)
alertController.addAction(selectAction)
alertController.addAction(cancelAction)
// Present the controller
self.presentViewController(alertController, animated: true, completion: nil)
}
}
func elcImagePickerController(picker: ELCImagePickerController!, didFinishPickingMediaWithInfo info:[AnyObject]!) {
NSLog("controller executed.")
}
You need to set the ImagePicker delegate
imagePicker.imagePickerDelegate = self
Are your NSLog statements ever getting called? One thing I notice in your trailing closure syntax is that you're using the type name versus a variable of that type. For instance, you're writing UIAlertAction in ... vs alertAction in .... You should be providing a name to be used within the closure rather than the type itself. If the rest of the closure is not executing, then the delegate is never being set, and therefore delegate methods are never called.

Dismissal of UIAlertController (best practice)

When using UIAlertController like this:
var alert = UIAlertController(title: "Core Location",
message: "Location Services Disabled!",
preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default,
handler: nil))
self.navigationController.presentViewController(alert, animated: true,
completion: nil)
I noticed that the dismissal of the alert view is seemingly done automatically.
Shouldn't the dismissal of a presented ViewController be done by the presenting ViewController via a delegate call?
The dismissal is "included" in the presentViewController call. You do not need a delegate because you have the completion block. In this block you put what you would normally put into the delegate callback, except the call to dismiss the alert.
As far as "best practice" is concerned, I noted that in many APIs, Apple replaced delegate callbacks with completion blocks. Apple typically recommends using the block syntax. I surmise this could be partly because it helps keeping the related code sections together.
Is some Cases you may like to use this:
class MyAlertController : UIAlertController {
var dismissHandler : (()->())?
override func viewDidDisappear(_ animated: Bool) {
dismissHandler?()
super.viewDidDisappear(animated)
}
}
Usage:
let alert = MyAlertController(title: ...
let cancelButton = UIAlertAction(titl
alert.dismissHandler = { /*...do something */ }
alert.addAction(cancelButton)
...
There is an elegant way! Just write the action or function inside the alert controller's cancel action. (here the action style should be .cancel)
Code for Swift 3:
let Alert: UIAlertController = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
let OkAction: UIAlertAction = UIAlertAction(title: “Ok”, style: .default) { ACTION in
//Will be called when tapping Ok
}
let CancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) {ACTION in
// Will be called when cancel tapped or when alert dismissed.
// Write your action/function here if you want to do something after alert got dismissed”
}
Alert.addAction(OkAction)
Alert.addAction(CancelAction)
present(Alert, animated: true, completion: nil)

Resources