UIAlertAction closure is not being called when presenting Controller is modal - ios

I have a UIViewController which is embedeed in UINavigationController which is being presented modally over my application's keyWindow.rootViewController. When I present UIAlertController in any screen of the presented navigation controller, the alert controller is correctly displayed but the closures for any of the UIAlertAction are not being called after being pressed.
I am displaying the same alert controller with the same code in view controllers belonging to the main navigation controller of my app and the closures are called properly.
The code for presenting the alert is very simple, here's the snippet:
// Creating Alert
func createAlert() -> UIAlertController {
let actions: [UIAlertAction] = [
UIAlertAction(title: "Something", style: .default, handler: { _ in
self.setOriginalQueue(with: items, description: description, identifier: identifier)
self.setQueue()
}),
UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
normal()
})
]
let alert = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet, actions: actions)
return alert
}
// In ViewController
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
So the question is, why are the closures not being called?

UIAlertController does not contain four types argument. See the document.
Once you fixed that, the rest will be automatically fixed like below.
For everyone's convenience I'll put the whole Modal view controller class source here.
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.present(createAlert(), animated: true, completion: nil)
}
func createAlert() -> UIAlertController {
let actions: [UIAlertAction] = [
UIAlertAction(title: "Something", style: .default, handler: { _ in
print("Something")
}),
UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
print("Cancel")
})
]
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)
for action in actions {
alert.addAction(action)
}
return alert
}
}

The line is wrong.
let alert = UIAlertController(title: nil, message: "message" , preferredStyle: .actionSheet, actions: actions)
You must be get error like that
Type of expression is ambiguous without more context
Change it like that:
return UIAlertController(title: YourTitle, message: YourMessage, preferredStyle: UIAlertController.Style.actionSheet)
So it can work.

Need to pass name of the method when you are presenting alert
self.present(createAlert(), animated: true, completion: nil)

Related

UIAlertController is not working in model class

I want to show an alert. But I want to show it by creating a function in another class and call that function from a viewcontroller. But it does not work.
Here is code from my LoginViewController:
class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
LoginModel().show_alert()
}
}
Here is code from my LoginModel:
class LoginModel{
let controller = LoginViewController()
public func show_alert(){
let alert = UIAlertController(title: "Title", message: "Some Message",
preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
controller.present(alert, animated: true, completion: nil)
}
}
You need to pass the reference of UIViewController subclass to your LoginModel class, to present the UIAlertViewController on LoginViewController. You should call show alert once the LoginViewController view is appeared on the screen, move the call to ViewWillApear or viewDidApear method
final class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidApear(animated)
LoginModel().show_alert(on: self)
}
}
final class LoginModel {
public func show_alert(on vc: UIViewController) {
let alert = UIAlertController(title: "Title", message: "Some Message",
preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
vc.present(alert, animated: true, completion: nil)
}
}
ideally you should not create the UI related methods in model classes, they should be on on UIViewController/UIView classes or their extension methods. Model classes should not know anything about UI stuff. So you can easily create simple extension method on UIViewController and call the showAlert method from viewController.
extension UIViewController {
func showAlert(_ title: String = "Alert", message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
present(alert, animated: true, completion: nil)
}
}
you can call this method from UIViewController like
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showAlert(message: "This is alert message")
}
Reminder: Your model shouldn't know about any UI related stuffs. Instead you should create an extension to UIViewController, or create free function
As a free function
func showAlertViewOnto(controller: UIViewController, detailInfo: (title: String?, message: String?), handler: ((UIAlertAction) -> Void)? = nil ) {
let alert = UIAlertController(title:detailInfo.title , message: detailInfo.message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
handler))
controller.present(alert, animated: true, completion: nil)
}
As an extension to your UIViewController
extension UIViewController {
func showAlertView(detailInfo: (title: String?, message: String?), handler: ((UIAlertAction) -> Void)? = nil ) {
let alert = UIAlertController(title:detailInfo.title , message: detailInfo.message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
handler))
self.present(alert, animated: true, completion: nil)
}
}
To Use it
showAlertViewOnto(controller: self, detailInfo: (title: "Hello ", message: "welcome to our service"), handler: { _ in
// here you can add code once ok is pressed
})
There is a logical error in your code. An instance of LoginViewController is already present (in the navigation stack or is the initial view controller) which appears on the screen.
You created a new instance of LoginViewController in your model class
let controller = LoginViewController()
which is not added to your navigation stack, so you don’t see it on the screen.
controller.present(alert, animated: true, completion: nil)
Presenting UIAlertController over the new instance will not show user an alert since the controller here itself is not present on the screen.
You will need to present UIAlertController from the instance visible on screen(the initial one). You could change the functionshow_alert to the following:
class LoginModel{
func showAlert(forController controller: UIViewController /*you could add title, message and other stuff here if needed.*/){
let alert = UIAlertController(title: "Title", message: "Some Message",
preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
controller.present(alert, animated: true, completion: nil)
}
And modify your call as follows:
LoginModel().showAlert(forController: self)

create a display alert function globally and call it from any view controller

func displayalert(title:String, message:String, vc:UIViewController)
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction((UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
self.dismiss(animated: true, completion: nil)
})))
vc.present(alert, animated: true, completion: nil)
}
this is the function i have used.i tried to call it like this,
displayalert1(title:"dsfvasdcs", message:"easfSDXCSDZX", vc:validateOTPViewController())
it is returning error "BAD ACCESS". the vc.present is running like a loop. I cant understand what the problem is.
I run your code and it working fine. I thing you would pass self in vc.
self.displayalert(title: "Title", message: "Some Message", vc: self)
You can also make an extension of UIViewController-
extension UIViewController {
// Your Function...
}
Now You can globally access this function from any view controller, Just by typing-
self.displayalert(title: "Title", message: "Some Message", vc: self)
You're passing a new instance of the validateOTPViewController to the displayalert function.
Change it to:
displayalert1(title:"dsfvasdcs", message:"easfSDXCSDZX", vc:self)
This will pass the current view controller to the function instead of a new one that hasn't been presented.
Swift 4
Create an extension of UIViewController with your function to display alert with required parameter arguments
extension UIViewController {
func displayalert(title:String, message:String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction((UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
alert.dismiss(animated: true, completion: nil)
})))
self.present(alert, animated: true, completion: nil)
}
}
Now call this function from your view controller:
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.displayalert(title: <String>, message: <String>)
}
}

create alert function in all view controllers - swift

I'm trying to declare a function for showing alerts in my app. To avoid repeating work, i'm trying to use same function for all my app. I tried to do that by creating a class with function showNotification. but when i create an object of that class and call the method, nothing happens. How can i do that?
class SharedPropertiesAndMetods : UIViewController {
func showNotification(title: String, message: String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "تائید", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
Use an extension like this
extension UIViewController {
func showAlert(title: String, message: String) {
let alertController = UIAlertController(title: title, message:
message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {action in
}))
self.present(alertController, animated: true, completion: nil)
}
}
call the function like this
self.showAlert(title: "hi", message: "test")
What I would do is to create a 'generic' view controller that do the job and than inherit from it:
1. If you want to display alert each time view did appear:
class GenericViewController: UIViewController {
// MARK: - View lifecycle -
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let notification = self.shouldDisplayAlertNotification() {
self.showNotification(notification)
}
}
// MARK: - Internal methods -
func shouldDisplayAlertNotification() -> AlertNotification? {
return nil
}
// MARK: - Private methods -
private func showNotification(_ alertNotification: AlertNotification) {
}
}
class MyController: GenericViewController {
override func shouldDisplayAlertNotification() -> AlertNotification? {
return AlertNotification(title: "Title", message: "Message")
}
}
Where AlertNotification is your custom model class:
class AlertNotification {
var title: String
var message: String
init(title: String, message: String) {
self.title = title
self.message = message
}
}
In this way, only VC that overrides shouldDisplayAlertNotificationwill display alert.
2. If you want to display alert on 'demand':
As suggested, extend UIViewController
extension UIViewController {
func showNotification(title: String, message: String) {
}
}
Actually you can declare a simple method anywhere outside class.
func showAlertWithCompletion(message:String,okTitle:String,cancelTitle:String?,completionBlock:#escaping (_ okPressed:Bool)->()){
let alertController = UIAlertController(title: AppName, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: okTitle, style: .default) { (ok) in
completionBlock(true)
}
alertController.addAction(okAction)
if let cancelTitle = cancelTitle{
let cancelOption = UIAlertAction(title: cancelTitle, style: .cancel, handler: { (axn) in
completionBlock(false)
})
alertController.addAction(cancelOption)
}
if let topController = UIWindow.topViewController(){
topController.present(alertController, animated: true, completion: nil)
}
}
This way wherever you call it, you will get ok button pressed callback in completion handle or even make Extension as described by #Ganesh Kumar
Why not just an extension
extension UIViewController {
func showNotification(title: String, message: String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "تائید", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
You can use this view controller extension to present alert view across the application.
https://github.com/SumitKr88/UIViewController-ShowAlertView/blob/master/UIViewController%2BExtensions.swift
extension UIViewController {
/// Show alert view
/// - Parameter title: title of alert
/// - Parameter message: message of alert
/// - Parameter actionTitles: List of action button titles(ex : "OK","Cancel" etc)
/// - Parameter style: Style of the buttons
/// - Parameter actions: actions repective to each actionTitles
/// - Parameter preferredActionIndex: Index of the button that need to be shown in bold. If nil is passed then it takes cancel as default button.
/**
Example usage:-
Just make sure actionTitles and actions array the same count.
/********** 1. Pass nil if you don't need any action handler closure. **************/
self.showAlert(title: "Title", message: "message", actionTitles: ["OK"], style: [.deafult], actions: [nil])
/*********** 2. Alert view with one action **************/
/// let okActionHandler: ((UIAlertAction) -> Void) = {(action) in
//Perform action of Ok here
}
self.showAlert(title: "Title", message: "message", actionTitles: ["OK", "CANCEL"], style: [.default, .cancel], actions: [okayActionHandler, nil])
/********** 3.Alert view with two actions **************/
let okActionHandler: ((UIAlertAction) -> Void) = {(action) in
//Perform action of ok here
}
let cancelActionHandler: ((UIAlertAction) -> Void) = {(action) in
//Perform action of cancel here
}
self.showAlert(title: "Title", message: "message", actionTitles: ["OK", "CANCEL"], style: [.default, .cancel], actions: [okActionHandler,cancelActionHandler], preferredActionIndex: 1)
*/
public func showAlert(title: String?,
message: String?,
actionTitles: [String?],
style: [UIAlertAction.Style],
actions: [((UIAlertAction) -> Void)?],
preferredActionIndex: Int? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: style[index], handler: actions[index])
alert.addAction(action)
}
if let preferredActionIndex = preferredActionIndex { alert.preferredAction = alert.actions[preferredActionIndex] }
self.present(alert, animated: true, completion: nil)
}}
You could create extension to alertController and also have option for action handler. This will allow to use two different Alert controller based on handler is required or not.
extension UIAlertControler {
class func genericErrorAlert(forAlert message: String, completion: ((UIAlertAction) -> Void)? = nil )
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: completion))
return alert
}
}
You can also create one util file in your app, in that you can add any reusable method or function and use it anywhere in your app Like,
import Foundation
import UIKit
//MARK: - ALERT
func showMessage(title: String, message: String!, VC: UIViewController) {
let alert : UIAlertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
}
alert.addAction(okAction)
VC.present(alert, animated: true, completion: nil)
}

Displaying an alert on a new View Controller

I have a button that sends me on another View Controller. What I am trying is to display an alert on the next View Controller.
In the viewDidLoad() method of the new controller, create a new UIAlertController and display it like the following
let alertController = UIAlertController(title: "Default Style", message: "A standard alert.", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
// ...
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action) in
// ...
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true) {
// ...
}
Note that this example was taken from the NSHipster website which offers nice articles about iOS. You can find the article about UIAlertController here. They also explain other stuff you can do with that class, like display an Action Sheet for example.
Swift 4
Create an extension of UIViewController with your function to display alert with required parameter arguments
extension UIViewController {
func displayalert(title:String, message:String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction((UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
alert.dismiss(animated: true, completion: nil)
})))
self.present(alert, animated: true, completion: nil)
}
}
Now call this function from your view controller:
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.displayalert(title: <String>, message: <String>)
}
}

Make a class comprises of the functions like UIAlertView, UIActivityIndicator and call them back in various viewControllers

This is my current code:
import UIKit
class classViewController: UIViewController {
// The function i want to call in other view controllers..
func alertView(title: String, message: String) {
var alert:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
In the other view controller, where I've made an IBAction to perform this alertView, I have done this:
#IBAction func button(sender: AnyObject) {
classViewController().alertView("title", message: "message")
}
When I run the app, after tapping the button I get this error, but no alertView:
Warning: Attempt to present on
whose view is not in the
window hierarchy!
Right. If you want to make a global class that displays alerts, you need to pass in a reference to the current view controller, and use that instead of "self" in calls like presentViewController.
Your class should probably not be a subclass of UIViewController, since it looks like you're never displaying it to the screen.
I created a Utils class that is a subclass of NSObject.
It has a method showAlertOnVC that looks like this:
class func showAlertOnVC(targetVC: UIViewController?, var title: String, var message: String)
{
title = NSLocalizedString(title, comment: "")
message = NSLocalizedString(message, comment: "")
if let targetVC = targetVC
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okButton = UIAlertAction(
title:"OK",
style: UIAlertActionStyle.Default,
handler:
{
(alert: UIAlertAction!) in
})
alert.addAction(okButton)
targetVC.presentViewController(alert, animated: true, completion: nil)
}
else
{
println("attempting to display alert to nil view controller.")
println("Alert title = \(title)")
println("Alert message = \(message)")
}
}

Resources