41

I would like to prevent the UIAlertController from dismissing.

I have a UIAlertAction that simply appends a string into the UIAlertTextField, however, once tapped it dismisses the view controller [undesired]. I've tried adding an NSNotification with undesired results.

    UIAlertAction *pasteMessage = [UIAlertAction actionWithTitle:@"Paste Message" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        UITextField *textField = alertC.textFields.firstObject;
        textField.text = [textField.text stringByAppendingString:[NSString stringWithFormat:@"%@", copiedString]];
    }];

I've also tried setting no to pasteMessage by:

 [alertC canPerformAction:@selector(dismissViewControllerAnimated:completion:) withSender:pasteMessage];

-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion {
    UIAlertController *alertController = (UIAlertController *)self.presentedViewController;
    UIAlertAction *paste = alertController.actions.firstObject;
       if (paste) {
         flag = NO;
       } else {
         flag = YES;
       }
 }

Edit, i'm not looking to prevent the tapping of UIAlertAction i'm looking to prevent the UIAlertController from dismissing when tapping on said action. The action can be enabled/disabled whatever, but my goal is to simply paste the copied message into the UITextField by pressing an action (hence the reason I don't want it to be dismissed)

I also realize setting the BOOL to dismissViewControllerAnimated: simply sets it to not animate the view controllers dismissal, I don't want it to imply it was for stopping the actual dismissal process. Simply offering the things I've tried in relation to my goal. I've also tried presenting a new UIAlertController when selecting pasteMessage auto-populating the new UIAlertControllers textField with the copied message, it works, but I feel like it's too hacky for what could be done.

soulshined
  • 9,612
  • 5
  • 44
  • 79
  • 2
    possible duplicate of [Prevent dismissal of UIAlertController](http://stackoverflow.com/questions/25628000/prevent-dismissal-of-uialertcontroller) – Lyndsey Scott Mar 07 '15 at 20:45
  • Thanks @LyndseyScott thats not a true solution to my question. That just prevents a button being enable based on the textfield having text in it : here is their solution : `= (tf.text != "")` that just disables the button from allowing the view from being dismissed _eventually_ – soulshined Mar 07 '15 at 20:47
  • But I think most relevant part of that answer is selecting a `UIAlertAction` *will* dismiss your alert. If you want some other behavior, try creating your own custom view. – Lyndsey Scott Mar 07 '15 at 20:55
  • Yeah @LyndseyScott i agree, I was just hoping someone had an alternate solution before I went that route, theres creative people out there. I looked at that question prior to posting, their main intent wasn't to prevent a dismiss it was to ensure text validation per their comments. – soulshined Mar 07 '15 at 20:57
  • It that ok when pressed on said button, paste the text you want to copy automatically? Or you make several choose actions for users to choose for what they want to. – Zigii Wong Mar 10 '15 at 04:29
  • 3
    I can't upvote your post enough. I'm in the same situation where I need to validate textbox and prevent alertbox from being dismissed when validation fails. The problem is that alertbox is dimssed right after user taps on the trigger button and my validation logic is inside that button tapped event handler. Seems that I have to build custom alertbox to resolve this. – KMC Sep 23 '16 at 11:51
  • Why is this not possible lol – Eric Aug 23 '22 at 04:57

2 Answers2

39

EDIT: Updated for Swift 5

EDIT: Updated to include @skywalker's feedback

So I actually got this to work. In short, it involves adding a long-press gesture recognizer to the UIAlertController that triggers before the dismissal occurs.

First, create lazily loaded computed variables in your view controller for your UIAlertController and the UIAlertAction you want to prevent from triggering so that self is accessible via the gesture recognizer's selector method you'll be attaching to the alert (self in the selector insinuates that all of this is inside a view controller).

lazy var alert: UIAlertController = {

    let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)

    alert.addTextField(configurationHandler: nil)

    let appendAction = self.appendAction
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)

    alert.addAction(appendAction)
    alert.addAction(cancelAction)

    let gestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(append(sender:)))
    gestureRecognizer.minimumPressDuration = 0.0
    alert.view.addGestureRecognizer(gestureRecognizer)

    return alert
}()

lazy var appendAction: UIAlertAction = {
    return UIAlertAction(title: "Paste Message", style: .default, handler: nil)
}()

Make sure your gesture recognizer above is a UILongPressGestureRecognizer set with a minimum press duration of 0. That way you can access the state of the gesture (for when the user touches down) before the action is triggered fully. There you can disable the UIAlertAction, implement your custom code, and reenable the action after the gesture has completed (user has touched up). See below:

@objc func append(sender: UILongPressGestureRecognizer) {
    
    switch sender.state {
    case .began:
        appendAction.isEnabled = false
    case .ended:
        // Do whatever you want with the alert text fields
        print(alert.textFields?[0].text)
        appendAction.isEnabled = true
    default:
        return
    }
}

Also, make sure that the view controller owning the presentation of this alert conforms to UIGestureRecognizerDelegate in order to recognize simultaneous gestures.

extension YourViewController: UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

Then, just present the UIAlertController wherever.

func showAlert() {
    self.present(alert, animated: true, completion: nil)
}

This is obviously a hack, but there's no other way that I know to achieve this without a hack since it's not meant to be achieved. For example, the gesture recognizer is tied to the UIAlertController so the user can trigger that method if they tap anywhere on the alert (besides the cancel button).

ORIGINAL ANSWER:

This is as close as I could come to a hack-a-round. If there was some way to customize the dismissal transition time to nothing then you could set animated: to false and it would look like the same alert, but I don't think it's possible

class ViewController: UIViewController {

    @IBAction func alert(sender: AnyObject) {
        let alert = UIAlertController(title: "title", message: "message", preferredStyle: .Alert)
    
        alert.addTextFieldWithConfigurationHandler(nil)
    
        let appendAction = UIAlertAction(title: "Append text", style: .Default) { _ in
            var textField = alert.textFields![0] as UITextField
            // Append text here
            self.presentViewController(alert, animated: true, completion: nil)
        }
    
        let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
    
        alert.addAction(appendAction)
        alert.addAction(cancelAction)
    
        self.presentViewController(alert, animated: true, completion: nil)
    }
}

I'm only familiar with swift

Aaron
  • 6,466
  • 7
  • 37
  • 75
  • Swift is no worries. Easily translatable. However, I'm not at a computer to trouble shoot but I'm pretty sure your code will cause an error. Your trying to present a view controller on the same one that already exists. Also, re your comment about setting animation to false, see my edit I made earlier prior to your answer, it was the 'workaround' I did but I'm not happy with it as a resolution which is why I ended up asking on here – soulshined Mar 10 '15 at 06:23
  • I ran the code in the simulator and it worked fine. Didn't see your edit, my bad. – Aaron Mar 10 '15 at 06:38
  • Your workaround was my work around. Up vote for the effort. Thanks for the input. Just going to create my own – soulshined Mar 17 '15 at 02:54
  • wow, sorry I'm getting to this a year late, but gesture recognizer is a fantastic idea. well done @Aaron – soulshined Oct 15 '17 at 03:51
  • 6
    @Aaron For those of you who found this answer to not work, set delegate for `gestureRecogniser` and implement `gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)` and return true in it.. Please verify and update your answer – Skywalker Jan 04 '18 at 03:49
  • @Skywalker, I've updated the answer to include your feedback. Thanks! – Aaron Apr 16 '21 at 16:12
  • 1
    This doesn't seem to be working anymore. Unless I'm missing something. – Houman Aug 16 '22 at 11:36
  • @Houman It didn't work for me until I added gestureRecognizer.delegate = self below the line gestureRecognizer.minimumPressDuration = 0.0 – Sami Iqneibi May 15 '23 at 15:17
0

Pretty much the same question is answered here

Text field on alert supports paste option, so there is no real reason to have separate button on alert to indicate "paste" option.

Otherwise you should mimic UIAlertController and reimplement it with desiread behavior.

Community
  • 1
  • 1
Nikita Ivaniushchenko
  • 1,425
  • 11
  • 11
  • It's not the same question. See my comments above. That question is for text validation. My question is strictly to prohibit dismissal. And There is a real reason for my circumstance. Just because there is a paste option doesn't mean it remembers the past 10 pastes. It will only paste what was last copied to the clipboard. My paste button is a singleton that's remembered app wide disregarding the iPhone's paste option (that way users can use that too if they want). Thanks for the input though - better something than nothing. – soulshined Mar 17 '15 at 02:49