Swift creating a function that runs another function that was implemented while called.(Not that complex) - swift

Hello i am trying to create a kickass function to show alerts and run it's function. Buuut unfortunately Xcode and i am getting confused in here:
buttonAction:Array<(Any) -> Any)>
Expected '>' to complete generic argument list
func callAlert(_ view: UIViewController, title:String, message:String, buttonName:Array<String>, buttonAction:Array<(Any) -> Any)>) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for index in 0..<buttonName.count{
alert.addAction(UIAlertAction(title: buttonName[index], style: .default, handler: { action in
switch action.style{
case .default:
print("default")
buttonAction()
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))}
view.present(alert, animated: true, completion: nil)
}
How do i call function? Please check below:
callAlert(self,
title: "Donate type",
message: "Thanks for your support!",
buttonName: ["Buy me a coffee!","Something"]
)

First of all I highly recommend to implement the method as an extension of UIViewController.
Second of all I'd prefer presentAlert() over callAlert()
Third of all rather than two arrays for buttons and actions use one array of tuples for title, style and action.
By the way unspecified type (Any) -> Any is very, very bad because UIAlertAction handlers are clearly ((UIAlertAction) -> Void)?
Finally add an optional completion handler
extension UIViewController {
func presentAlert(title: String,
message: String,
alertActions: [(title: String, style: UIAlertAction.Style, action: ((UIAlertAction) -> Void)?)],
completion: (() -> Void)? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for action in alertActions {
alert.addAction(UIAlertAction(title: action.title, style: action.style, handler: action.action))
}
self.present(alert, animated: true, completion: completion)
}
}
And use it inside an UIViewController
let buyCoffeeAction : (UIAlertAction) -> Void = { action in
// do something
}
let somethingAction : (UIAlertAction) -> Void = { action in
// do something
}
presentAlert(title: "Donate type",
message: "Thanks for your support!",
alertActions: [(title: "Buy me a coffee!", style: .default, action: buyCoffeeAction),
(title: "Something", style: .destructive, action: somethingAction)],
completion: nil)

Related

Swift: How to call two times the same alert from another alert?

I have simplified my problem to the following code where the same behavior occurs.
What I want to do is calling a method which includes an alert (messageWindow() in my code) two times which means one behind the other. I want to call that method from another method which also includes an alert (userInput()) in my code.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
userInput()
}
func userInput() {
let alert = UIAlertController(
title: "Welcome",
message: "Do you want say hello?",
preferredStyle: .alert)
let actionYes = UIAlertAction(
title: "Yes",
style: .default) {_ in
print("hello")
self.messageWindow(title: "1st call", message: "Hello!")
self.messageWindow(title: "2nd call", message: "Hello!!")
}
let actionNo = UIAlertAction(
title: "No",
style: .default) { (action) in }
alert.addAction(actionYes)
alert.addAction(actionNo)
self.present(alert, animated: true)
}
func messageWindow (title: String, message: String) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: .alert)
let actionOk = UIAlertAction(title: "OK", style: .default) { (action) in }
alert.addAction(actionOk)
self.present(alert, animated: true)
}
}
My problem is that the second call won't be executed (code snippet below). That means I don't see a window popping up like in the first call.
self.messageWindow(title: "2nd call", message: "Hello!!")
I'm relative new in coding with Swift. Please excuse my question in case it is a really simple one. I didn't found anything which helped me solving this problem.
I appreciate your help.
Thanks.

How can I output UIAlertController to a separate file and output data from there?

I'm trying to output alert to a separate function, since there will be many similar ones.
Here is my alert:
extension UIViewController {
func alertEditSum(nameCell: String, completion: (() -> Void)) {
let alertController = UIAlertController(title: "Hello", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default , handler: { _ in
let nameFolderField = alertController.textFields![0] as UITextField
if nameFolderField.isTextFieldCheck(text: nameFolderField.text!) == true {
// -----here----
}
}))
alertController.addAction(UIAlertAction(title: "Cancel"
, style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: { (nameField: UITextField!) -> Void in
nameField.clearButtonMode = .always
nameField.keyboardType = .decimalPad
})
self.present(alertController, animated: true)
}
}
and my piece of code is in another VC:
self.sortedDate[indexPath.section-1].personPayment = Double(nameFolderField.text!)!
do {
try! self.context.save()
collectionView.reloadData()
}
The problem is that I need to consider what exactly the user enters in UITextField. text! (nameFolderField.text!). I also can't add completion to the alert code, writes an error.
Completion should be added to the line where it says: / / - - - - - here----
Please tell me how to solve this problem?
This should do the trick:
func alertEditSum(nameCell: String, completion: #escaping ((String?) -> Void)) {
let alertController = UIAlertController(title: "Hello", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { _ in
let nameFolderField = alertController.textFields![0] as UITextField
if nameFolderField.isTextFieldCheck(text: nameFolderField.text!) == true {
completion(nameFolderField.text)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
completion(nil)
}))
alertController.addTextField(configurationHandler: { nameField in
nameField.clearButtonMode = .always
nameField.keyboardType = .decimalPad
})
self.present(alertController, animated: true)
}
To call it:
theViewController.alertEditSum(nameCell: "text") { text in
if let text = text {
//Do stuff
} else { //Text is invalid or user has cancel
}
}
Now, isTextFieldCheck(text:) is I guess a method on UITextField, since it's checking its own text, why giving it as a parameter?
Why not just func isTextValid()?
I would also avoid the force unwrap: !.
Going further, would be to use Result<String, Error> in the completion:
completion((Result<String, Error>) -> Void)) to have more infos if needed (user has canceled, text wasn't not valid for any reason, etc.)
You neeed #escaping completion to send value from inside a closure to a caller , Main changes completion:#escaping (String?) -> Void) and completion(nameFolderField.text)
extension UIViewController {
func alertEditSum(nameCell: String,completion:#escaping (String?) -> Void) {
let alertController = UIAlertController(title: "Hello", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default , handler: { _ in
let nameFolderField = alertController.textFields!.first!
if nameFolderField.isTextFieldCheck(text: nameFolderField.text!) {
completion(nameFolderField.text)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: { (nameField: UITextField!) -> Void in
nameField.clearButtonMode = .always
nameField.keyboardType = .decimalPad
})
self.present(alertController, animated: true)
}
}
Call
alertEditSum(nameCell:<#SomeValue#>) { result in
print(result)
}

Using a escape function in Swift to allow the use of parameters

I am new to this escape function in Swift, but I follow a tutorial and I use the following function below: (the function is working for me)
static func showThreeOptions(messageText: String, titleOne:String, titleTwo: String, actionOne: #escaping () -> (Void), actionTwo: #escaping () -> (), currentView: UIViewController ) {
// create the alert
let alert = UIAlertController(title: "Alerta", message: messageText, preferredStyle: UIAlertController.Style.alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: titleOne, style: UIAlertAction.Style.default, handler: { (alert) in
actionOne()
} ))
alert.addAction(UIAlertAction(title: titleTwo, style: UIAlertAction.Style.default, handler: { (alert) in
actionTwo()
} ))
alert.addAction(UIAlertAction(title: "Cancelar", style: UIAlertAction.Style.destructive, handler: nil))
// show the alert
currentView.present(alert, animated: true, completion: nil)
}
Now, I want to change the actionTwo() to actionTwo(number:Int),
but I don't know how to change the signature actionTwo: #escaping () -> ()
How can I change the signature
actionTwo: #escaping () -> () to allow to be able to call actionTwo(number:Int) ?
-----UPDATE-----
I create the function
actionTwo(2) and it works. Thank you #RobNapier
But there is another problem now.
I call the function
AlertActions.showThreeOptions(
messageText: "Resenha Finalizada.",
titleOne: "Marcas/Fotos",
titleTwo: "Editar",
actionOne: self.someHandlerOne,
actionTwo: self.someHandlerTwo(2),
currentView: self
)
This is the functions
func someHandlerOne() {
print("test")
}
func someHandlerTwo(_ id:Int) {
print("test2")
}
Now I get the following error when I call someHandlerTwo(_ id:Int)
Cannot convert value of type '()' to expected argument type '(Int) -> ()'
How can I fix that error?
-----UPDATE 2-----
I find out how to use a escaping function now
func notImplemented(resDado_id: Int) -> () {
print(resDado_id)
}
Change #escaping () -> () to #escaping (Int) -> (). Instead of something that takes no parameters, you want something that takes one.
It's a little nicer to use Void for return values that are (), like (Int) -> Void, but it means the same thing.
My suggestion is a different approach:
Write an extension of UIViewController and use the UIAlertAction handler signature for actionOne and actionTwo.
This is still more versatile and the UIAlertAction handler closures don't escape
extension UIViewController {
func showThreeOptions(messageText: String, titleOne:String, titleTwo: String, actionOne: ((UIAlertAction) -> Void)? = nil, actionTwo: ((UIAlertAction) -> Void)? = nil) {
// create the alert
let alert = UIAlertController(title: "Alerta", message: messageText, preferredStyle: .alert)
// add the actions (buttons)
alert.addAction(UIAlertAction(title: titleOne, style: .default, handler: actionOne))
alert.addAction(UIAlertAction(title: titleTwo, style: .default, handler: actionTwo))
alert.addAction(UIAlertAction(title: "Cancelar", style: .destructive, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
}
}
The closures can even be declared as functions for example
func actionOne(action : UIAlertAction) {
//
}
Edit:
You don't need to pass a parameter, you can create the handler inline and capture the id
func deleteSomething(at id: Int) {
let handler : (UIAlertAction) -> Void = { action in
db.deletarResDados(id: id)
}
showThreeOptions(messageText: "Resenha Finalizada.",
titleOne: "Marcas/Fotos",
titleTwo: "Editar",
actionOne: nil,
actionTwo: handler)
}

How to create a reusable UIAlert ActionSheet as an UIViewController extension?

I would like to create an action sheet that can be used several time in my code. To do so, I need to be able to use functions according to the action sheet title. Is there a way to pass functions as a parameter array like the "title" parameter?
//MARK: - UIAlert action sheet title
enum ActionSheetLabel: String {
case camera = "Camera"
case photoLibrary = "Album"
case cancel = "Cancel"
}
class CameraHandler {
static let cameraHandler = CameraHandler()
func openCamera() { }
func openPhotoLibrary() { }
}
//MARK: - Alert that shows an action sheet with cancel
extension UIViewController {
func showActionSheetWithCancel(vc: UIViewController, title: [ActionSheetLabel] /*Make a function parameter here to match title*/) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for value in title {
actionSheet.addAction(UIAlertAction(title: value.rawValue, style: .default, handler: {
(alert: UIAlertAction!) -> Void in
//Use the parameter function here to match title
}))
}
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel, handler: nil))
vc.present(actionSheet, animated: true, completion: nil)
}
}
For UIAlert you just need to change preferredStyle .alert it and it's working for UIAlert And and below code just copy and paste it working for UIActionSheet.
extension UIViewController {
func popupAlert(title: String?, message: String?, actionTitles:[String?], actionStyle:[UIAlertAction.Style], actions:[((UIAlertAction) -> Void)?]) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: actionStyle[index], handler: actions[index])
alert.addAction(action)
}
self.present(alert, animated: true, completion: nil)
}
}
Check below code For Usage
self.popupAlert(title: "Alert"), message: “Error in Loading”, actionTitles: ["Okey", "Email"], actionStyle: [.default, .default], actions: [nil,{ action in
// I have set nil for first button click
// do your code for second button click
}])
if you have any query then please comment me. Thank You
I have find out the best way to add an action sheet with cancel and as much action as needed.
Create an UIViewController extension with type alias:
//MARK: - Alert that shows an action sheet with cancel
extension UIViewController {
typealias AlertAction = () -> ()
typealias AlertButtonAction = (ActionSheetLabel, AlertAction)
func showActionSheetWithCancel(titleAndAction: [AlertButtonAction]) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for value in titleAndAction {
actionSheet.addAction(UIAlertAction(title: value.0.rawValue, style: .default, handler: {
(alert: UIAlertAction!) -> Void in
value.1()
}))
}
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
}
}
Then, in the class or other place where you want to use it, add the method this way:
//MARK: - UIAlert action sheet title
enum ActionSheetLabel: String {
case camera = "Camera"
case photoLibrary = "Album"
case cancel = "Cancel"
}
//MARK: - Class example where to use the action sheet action
class CameraHandler {
fileprivate let currentVC: UIViewController!
func openCamera() {
// Open user camera
}
func openPhotoLibrary() {
// Open user photo library
}
// Method example of this action sheet
func showActionSheetWithCameraAndLibrary(vc: UIViewController) {
//This is the way to use the extension
vc.showActionSheetWithCancel(titleAndAction: [
(ActionSheetLabel.camera, { [weak self] in self?.openCamera() }),
(ActionSheetLabel.photoLibrary, { [weak self] in self?.openPhotoLibrary() })
])
}
}
You can pass a closure and call it in the handler something like this should work.
Also not sure why you were passing the UIViewController , as you're already defining the function in a extension UIViewController therefore i allowed my self to remove it and used self.present instead .
extension UIViewController {
func showActionSheetWithCancel(title: [ActionSheetLabel], action: #escaping () -> ()?) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for value in title {
actionSheet.addAction(UIAlertAction(title: value.rawValue, style: .default, handler: {
(alert: UIAlertAction!) -> Void in
// action
action()
}))
}
let alertAction = UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel) { (_) in
action() // or for cancel call it here
}
actionSheet.addAction(alertAction)
self.present(actionSheet, animated: true, completion: nil)
}
}
As you can see #escaping () -> ()? is optional so you can pass nil too .
from what I understood you need to call a specific functions when the title of the alert changes & also you want to be able to do so from different viewControllers,
I hope this will help
extension UIViewController {
func showActionSheetWithCancel(vc: UIViewController, title: [ActionSheetLabel] ) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cameraHandler = CameraHandler()
for value in title {
switch value.rawValue {
case ActionSheetLabel.camera.rawValue:
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.camera.rawValue, style: .default, handler: { (alert) in
cameraHandler.openCamera()
}))
case ActionSheetLabel.photoLibrary.rawValue:
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.photoLibrary.rawValue, style: .default, handler: { (alert) in
cameraHandler.openPhotoLibrary()
}))
default:
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel, handler: nil))
}
vc.present(actionSheet, animated: true, completion: nil)
}
}
}
and the call of the function will be like this:
showActionSheetWithCancel(vc: self, title: [UIViewController.ActionSheetLabel.camera])

Showing alert for errors with Swift 4

The following code gives me an sigabort when .present is called:
func alert(message: String, title: String = "") {
let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "Ok", style: .default, handler: { action in
switch action.style {
case .default:
print("default")
case .cancel:
print("cancel")
case .destructive:
print("destructive")
}}))
alertController.present(alertController, animated: true, completion: nil)
}
Help! I am not getting this!
You're calling the alertController's present instead of the view's. Change it to refer to the parent UIViewController's present(...).
func present(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)? = nil)
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-present