Prevent UIAlertController to dismiss
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
Prevent dismissal of UIAlertController
You're correct: if the user can tap a button in your alert, the alert will be dismissed. So you want to prevent the user from tapping the button! It's all just a matter of disabling your UIAlertAction buttons. If an alert action is disabled, the user can't tap it to dismiss.
To combine this with text field validation, use a text field delegate method or action method (configured in the text field's configuration handler when you create it) to enable/disable the UIAlertActions appropriately depending on what text has (or hasn't) been entered.
Here's an example. We created the text field like this:
alert.addTextFieldWithConfigurationHandler {
(tf:UITextField!) in
tf.addTarget(self, action: "textChanged:", forControlEvents: .EditingChanged)
}
We have a Cancel action and an OK action, and we brought the OK action into the world disabled:
(alert.actions[1] as UIAlertAction).enabled = false
Subsequently, the user can't tap OK unless there is some actual text in the text field:
func textChanged(sender:AnyObject) {
let tf = sender as UITextField
var resp : UIResponder = tf
while !(resp is UIAlertController) { resp = resp.nextResponder() }
let alert = resp as UIAlertController
(alert.actions[1] as UIAlertAction).enabled = (tf.text != "")
}
EDIT Here's the current (Swift 3.0.1 and later) version of the above code:
alert.addTextField { tf in
tf.addTarget(self, action: #selector(self.textChanged), for: .editingChanged)
}
and
alert.actions[1].isEnabled = false
and
@objc func textChanged(_ sender: Any) {
let tf = sender as! UITextField
var resp : UIResponder! = tf
while !(resp is UIAlertController) { resp = resp.next }
let alert = resp as! UIAlertController
alert.actions[1].isEnabled = (tf.text != "")
}
Prevent UIAlertController from dismissing on UIAlertAction
You can check the version of the app in appDelegate.
func applicationDidBecomeActive(_ application: UIApplication) {
//check the app version here and if version mismatch is there show the alert.
// if the version is different then make initial viewController as root view controller. and then present alert from that view controller.
}
Prevent Alert action from dismissing the UIAlertController if incorrect answer - Swift 4
You can't do this.
It's system behaviour - when you click on button alert closes and you can't prevent this.
Only one solution is to create a custom view controller that will look like native UIAlertController
.
Dismissal of UIAlertController (best practice)
The dismissal is "included" in the presentViewController
call. You do not need a delegate because you have the completion block. In this block you put what you would normally put into the delegate callback, except the call to dismiss the alert.
As far as "best practice" is concerned, I noted that in many APIs, Apple replaced delegate callbacks with completion blocks. Apple typically recommends using the block syntax. I surmise this could be partly because it helps keeping the related code sections together.
Related Topics
Inter-App Data Migration (Migrating Data to New App Version)
How to Update the Constant Height Constraint of a Uiview Programmatically
Rounding a Double Value to X Number of Decimal Places in Swift
Protocol Doesn't Conform to Itself
What Is an Optional Value in Swift
Firestore: How to Get Random Documents in a Collection
Swift 3 Incorrect String Interpolation With Implicitly Unwrapped Optionals
Any Way to Replace Characters on Swift String
How to Deal With @Objc Inference Deprecation With #Selector() in Swift 4
Is Swift Pass by Value or Pass by Reference
How Does One Generate a Random Number in Swift
Difference Between 'Let' and 'Var' in Swift
Nsdate() or Date() Shows the Wrong Time
How to Check If a String Contains Another String in Swift
Structure VS Class in Swift Language