cannot dismiss view controller via closure in UIAlert - ios

All,
I am trying to write a method that will pass a closure to UIAlertAction such that when the OK button on an alert is tapped, both the alert and the calling view controller are dismissed.
What I have is:
func displayErrMsg( ecode : errorCodes ) ->() {
var etitle = ""
var etext = ""
var completionHandler: (()->())?
switch ecode {
case .NoError :
etitle = "Found You!"
etext = "Check your email for a link to reset your password"
completionHandler = { self.dismissViewControllerAnimated(true, { println("BUHBYE") })}
case .EmailAddressNotFound :
etitle = "Sorry, but we could not find you."
etext = "Have you registered?"
case .MalFormedEmailAddress :
etitle = "Opps!"
etext = "that is not a valid email address"
default : println(" unrecognized error code: \(ecode)")
}
userMessage( self, etitle, etext, completionHandler )
}
which calls this:
public func userMessage(parent: UIViewController, title:String, message:String, completion:(() ->())?) {
var okButton : UIAlertAction
var alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
if let comp = completion {
okButton = UIAlertAction(title: "Ok", style: .Default, handler: { (alert) -> Void in
comp()
})
}
else
{
okButton = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, nil)
}
alert.addAction(okButton)
parent.presentViewController(alert, animated: true, completion: nil)
}
In the scenario where the ecode is .NoError, I see the "BUHBYE" printed in the console,
but the view is not removed after the OK button is tapped (the alert is removed). From what I know, the view controller that should be dismissed by the closure is the one from which the displayErrorMsg
is called. Correct? Why doesn't this work?
Any and all help greatly appreciated.
:bp:

I'm not familiar yet with swift, but if the logic is the same as Objective-C, you may want to present this view controller on the didDismiss alert view call, not the clickedButton :
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
Also note that you can not remove a view controller presenting an alert view from the hierarchy until the alert view is actually dismissed.

Related

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

Swift UIAlertController disable return key without UITextField

Alright, I've looked up a lot of documentation on how the UIAlertController's work and I have a rather unique scenario. My application is being designed to work with a HID Bluetooth scanner. When I use:
preferredStyle: UIAlertControllerStyle.Alert
After I generate an alert that the item I have scanned is incorrect. Cool, alert is happening, problem is if I scan again ( which emulates keyboard input ), the return key is being sent to the alert and the alert is running the dismiss action.
If I use:
preferredStyle: UIAlertControllerStyle.ActionSheet
Then the alert is staying where it should be and ignoring scans while the alert window is up just how I want it.
So my question is, how do I capture the return key and prevent the Alert from calling the dismiss action? I'm a bit new with swift and I have a feeling the answer is simple, but I've tried half a dozen things that just isn't working.
If there is a setting to prevent all user input to the alert window or anything solution, I'm all for any method. I just rather not use the ActionSheet, and prefer to use the iOS alerts instead of creating my own screen. If this is not possible, I'm sure I can build my own 'alerts' window.
Code, that I'm calling from a simple Alerts class I made.
import UIKit
class Alerts {
var controller: UIViewController
var message: String
var title: String
init?(title: String, message: String, controller: UIViewController){
if title.isEmpty || message.isEmpty {
return nil
}
self.title = title
self.message = message
self.controller = controller
}
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
}
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
}
Try something like this.
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
showAlert()
}
showAlert()
}
func showAlert() {
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}

Check if alertview is displayed

In my app I display tow alert views. The second alert view should pop up if the first has been closed. Now I check if an alert view is displayed like this:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let viewController = appDelegate.window!.rootViewController as! ViewController
if viewController.view.window != nil {
}
else {
let alertView = UIAlertController(title: NSLocalizedString("IAP", comment: "comment"), message: NSLocalizedString("IAP1", comment: "comment"), preferredStyle: .Alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .Cancel, handler: nil))
viewController.presentViewController(alertView, animated: true, completion: nil)
}
I send the 2nd alert view if the first isn't displayed anymore. But if the first view is still displayed the 2nd alert view doesn't pops up anymore. So my question is if there's a waiting line for alert views and how can I solve this problem?
You should define a handler for the first action and present the 2nd alertView within the handler.
So instead of
UIAlertAction(title: "Ok", style: .Cancel, handler: nil)
you should do
UIAlertAction(title: "Ok", style: .Cancel) { (action) -> Void in
// Present the other alertView
}
If you are using a navigation controller, the general way to see if an alert is already displayed is to check the presentedViewController property.
if let _ = navigationController.presentedViewController {
print("is already presenting \(navigationController.presentedViewController)")
} else {
navigationController.presentViewController(alert, animated:true, completion:nil)
}
For a fast patch in any UIViewController : (Xcode 8.3.1 & Swift 3.1)
func blabla() {
if presentedViewController != nil {
delay(0.5, closure: {
self.blabla()
})
return
}
// other alert code here
}
Simple way to check in Swift
var isAlertViewPresenting: Bool {
get {
if self.presentedViewController is UIAlertController {
return true
}
return false
}
}

UIAlertController in swift

I am creating a view controller in swift with a few text fields and an accept button which confirms the user's input. The accept button also checks if any of the text fields is empty. If so, it will pop up an alert saying something like it cannot be empty. if it is not empty, it will store the input and then jump to another view.
I created an separated function called checEmpty() which looks like this:
func checEmpty(title: String, object: UITextField) -> (Bool) {
if object.text.isEmpty {
let alertController = UIAlertController(title: "Invalid input",
message:"\(title) cannot be empty",
preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss",
style: UIAlertActionStyle.Default)
self.presentViewController(alertController, animated: true, completion: nil)
return false
} else {
return true
}
}
And I call this function in the acceptButton action:
#IBAction func acceptButton(sender: UIButton){
if(checEmpty("Event", object: eventName) && checEmpty("Priority", object: Priority)
{
//if not empty, confirm the user input
// ...
}
When I run it, the alert message works fine but for some reason the console shows this:
2015-08-03 12:11:50.656 FinishItToday[13777:688070] >'s window is not equal to
's view's window!
Can anyone tell me why this warning appears? Thank you very much!
PS.
What I want it to do is that if any of the text field is empty, show the alert and then stay at the same page. If none of them are empty, then perform the segue and switch to another view. The code above works fine except the warning.
Here is your working code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var eventName: UITextField!
#IBOutlet weak var Priority: UITextField!
#IBAction func acceptButton(sender: UIButton){
if checEmpty("Event", object: eventName) && checEmpty("Priority", object: Priority){
println("Both Text Fields Are Empty")
}
}
func checEmpty(title: String, object: UITextField) -> (Bool) {
if object.text.isEmpty {
var Alert = UIAlertController(title: "Invalid input", message: "\(title) cannot be empty", preferredStyle: UIAlertControllerStyle.Alert)
Alert.addAction(UIAlertAction(title: "Dismiss", style: .Cancel, handler: { action in
println("Click of cancel button")
}))
self.presentViewController(Alert, animated: true, completion: nil)
return false
} else {
return true
}
}
}
Use this code to for alert view controller in swift. It may help you.
import UIKit
protocol alertViewDelegate {
func actionActive(index:Int, tag:Int)
}
class AlertView: NSObject {
var delegate:alertViewDelegate!
func showAlert(title:String, message:String, actionName:NSArray, tag:Int) -> UIAlertController {
var alertController:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
for str: AnyObject in actionName {
let alertAction:UIAlertAction = UIAlertAction(title: str as! String, style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.delegate.actionActive(actionName.indexOfObject(str), tag:tag)
})
alertController.addAction(alertAction)
}
return alertController;
}
}

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