Showing alert for errors with Swift 4 - swift

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

Related

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)
}

I am trying to add specific handlers and alertStyles to my alerts, but I am using a global alert which doesn't have a handler

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)
}

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])

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

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)

How do we create and dismiss an UIAlertController without user input? (Swift)

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.