Dismiss UIAlertViewController by tapping outside - ios

I want to dismiss my UIAlertViewController by tapping outside of the UIAlertViewController, on tapping on black screen space. I've tried this:
self.presentViewController(alertViewController, animated: true, completion:{
alertViewController.view.superview?.userInteractionEnabled = true
alertViewController.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertClose(_:))))
})
but it just closes if I tapped on UIAlertViewController. But I want outside, where half-blacked screen tapped.
Is it possible?
UPDATE
#IBAction func shareButtonTapped(sender: UIButton) {
let alertViewController = UIAlertController(title: "Share on social networks", message: "Where do you want to share?", preferredStyle: .ActionSheet)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "dismissAlertView:")
self.view.addGestureRecognizer(tapGestureRecognizer)
let facebookAction = UIAlertAction(title: "Facebook", style: .Default) { (alert: UIAlertAction) -> Void in
}
let twitterAction = UIAlertAction(title: "Twitter", style: .Default) { (alert: UIAlertAction) -> Void in
}
let cancelAction = UIAlertAction(title: "Cancel", style: .Destructive) { (alert: UIAlertAction) -> Void in
}
alertViewController.addAction(facebookAction)
alertViewController.addAction(twitterAction)
alertViewController.addAction(cancelAction)
self.presentViewController(alertViewController, animated: true, completion:{
alertViewController.view.superview?.userInteractionEnabled = true
alertViewController.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertClose(_:))))
})
}
AlertClose function:
func alertClose(gesture: UITapGestureRecognizer) {
self.dismissViewControllerAnimated(true, completion: nil)
}

If you are using ActionSheet style there is no need to do this. Because when you tap on half-blacked screen view controller automatically will be dismissed.
But if you want to use with Alert style do following (without: alertViewController.view.superview?.userInteractionEnabled = true) :
self.presentViewController(alertViewController, animated: true, completion: {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.alertClose(_:)))
alertViewController?.view.superview?.addGestureRecognizer(tapGestureRecognizer)
})

Try to add UITapGestureRecognizer to your UIWindow class like
view.window.addGestureRecognizer()
implement UIGestureRecognizerDelegate method shouldReceiveTouch
and check your tapped view
Hope this help

Related

Tap gesture in a view controller causing delay in opening of the view controller when a button in other view controller is pressed

I have implemented a tap gesture on UIStackView in one ViewController which is embedded in a Tab Bar. When I click button on tab bar to open it, it causes a delay of few seconds and then opens the View Controller. After searching for a while on internet I found out tap gestures might cause this and apparently when I remove this tap gesture, it works smoothly. The solution they suggested was just remove tap gesture as in most cases they did not want it and it was accidentally there however I do want tap gestures so I cannot remove it. This is how I have added a simple tap gesture in viewDidLoad():
let tapAddImage = UITapGestureRecognizer(target: self, action: #selector(self.addImage(_:)))
tapAddImage.cancelsTouchesInView = false
svAddImage.addGestureRecognizer(tapAddImage)
This function opens image picker.
Also I just noticed the same issue is caused in other ViewControllers too where I have implemented imagePicker which is opened by tapping something using Tap Gesture. Here is my image picker delegate functions:
#objc func addImage(_ sender: UITapGestureRecognizer) {
openCameraOrGallery()
}
func openCameraOrGallery() {
let alert = UIAlertController(title: BaseUrl.shared.projectName, message: "Select Option", preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Camera", style: .default , handler:{ (UIAlertAction)in
self.present(self.imagePicker!, animated: true, completion: {
self.imagePicker?.sourceType = .camera
self.imagePicker?.delegate = self
})
}))
alert.addAction(UIAlertAction(title: "Gallery", style: .default , handler:{ (UIAlertAction)in
self.present(self.imagePicker!, animated: true, completion: {
self.imagePicker?.sourceType = .photoLibrary
self.imagePicker?.delegate = self
})
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler:{ (UIAlertAction)in
print("User click Dismiss button")
}))
self.present(alert, animated: true, completion: {
print("completion block")
})
}
//MARK: UIImagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let tempImage:UIImage = info [.originalImage] as? UIImage else {return}
self.ivSelectedPostImage.image = tempImage
let imageData:NSData = tempImage.jpegData(compressionQuality: 0.2)! as NSData
postBase64 = imageData.base64EncodedString(options: .lineLength64Characters)
self.svAddPostContentButtons.isHidden = true
self.viewSelectedPostImage.isHidden = false
self.dismiss(animated: true, completion: nil)
}
func cancelButtonDidPress(_ imagePicker: UIImagePickerController) {
imagePicker.dismiss(animated: true, completion: nil)
}
I have just copy pasted this code from my other project. There is no issue in any other project with ImagePicker Delegates or tap gestures. Any idea what might be causing delay only in these pages where I open camera or gallery by using tap gestures? This is only happening to those ViewControllers where im clicking a button to open these ViewControllers having tap gestures.
Please try to initialise the tap gesture in ViewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let tapAddImage = UITapGestureRecognizer(target: self, action: #selector(self.addImage(_:)))
tapAddImage.cancelsTouchesInView = false
svAddImage.addGestureRecognizer(tapAddImage)
}

Check if UIAlertController action sheet is dismissed through cancel button?

I have implemented action sheet though alert controller. I want to display a button like cancel button with "Pay" Text written on it.
Issue is makeCall() function call when pay button is clicked , And when rest of the screen is tapped makeCall() function is called again.
How can I identify that action is called through pay button action or through Tapp on rest of the screen? I only want to make call to makeCall() function when pay button is tapped.
alertController = UIAlertController(title: "", message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Pay", style: .cancel) { (UIAlertAction) in
printLog("cancelAction")
makeCall()
}
cancelAction.isEnabled = false
alertController.addAction(cancelAction)
self.present(alertController, animated: true) {}
Here, alert controller view userInteraction disable so when tap outside alert controller not close.
You can do like this:
self.present(alertController, animated: true){
if let mainView = alertController.view.superview?.subviews[0]{
mainView.isUserInteractionEnabled = false
}
}
OR
self.present(alertController, animated: true) {
if let allContainerView = alertController.view.superview?.subviews{
for myview in allContainerView{
if (myview.gestureRecognizers != nil){
myview.isUserInteractionEnabled = false
}
}
}
}
I hope it will work for you.
I don't find any solution that how can you identify that action is called through pay button action or through Tap on rest of the screen.
but the alter solution is that you add tap gesture on rest view. So, you can identify that if cancel button tap or tap in rest of the screen.
alertController = UIAlertController(title: "", message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Pay", style: .cancel) { (UIAlertAction) in
printLog("cancelAction")
makeCall()
}
alertController.addAction(cancelAction)
self.present(alertController, animated: true) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissAlertController))
alertController.view.superview?.subviews[0].addGestureRecognizer(tapGesture)
}
#objc func dismissAlertController(){
self.dismiss(animated: true, completion: nil)
print("through Tap on rest of the screen")
}

Add a condition to a modal push segue [duplicate]

This question already has answers here:
Perform segues when I want
(2 answers)
Closed 4 years ago.
I use a navigation controller and modal show segues.
I want to block the back button of my second view controller if [condition] isn't true, for exemple by adding an alert "You can't go back until [condition]" when the user press the back button.
Don't know if it's possible, if someone know how to solve it !
Thanksss
You need to add a custom action for your back bar button item.
#IBAction func backPressed(_ sender: UIBarButtonItem) {
if !myCondition {
let alert = UIAlertViewController(title: "Alert", message: "Please fulfill the condition")
let action = UIAlertAction(title: "OK", .default)
self.present(alert, animated: true)
return
}
navigationController?.popViewController(animated: true)
}
Or you can just use normal target action from code.
myBarButton.addTarget(self, selector: #selector(backButtonPressed(_:)))
Sorry for incorrect syntax.
Need back button image "ic-menu-back-primary"
override func viewDidLoad() {
super.viewDidLoad()
// Nav Back Button
self.navigationItem.hidesBackButton = true
let backButton = UIBarButtonItem(image: #imageLiteral(resourceName: "ic-menu-back-primary"), style: .plain, target: self, action: #selector(back(_:)))
navigationItem.leftBarButtonItem = backButton
}
#IBAction func back(_ sender: Any?) {
let alert = UIAlertController(title: nil, message: "You can't go back until [condition]", preferredStyle: .alert)
let resume = UIAlertAction(title: "Cancel", style: .cancel)
let cancel = UIAlertAction(title: "Exit", style: .default) { (action) in
self.navigationController?.popViewController(animated: true)
}
alert.addAction(resume)
alert.addAction(cancel)
present(alert, animated: true)
}

UIAlertController, eliminate the animation on closing?

Here's a simple action sheet,
let choice = UIAlertController(title: "Choose", message: nil, preferredStyle: .actionSheet)
choice.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
self.happyCamera() }))
choice.addAction(UIAlertAction(title: "Album", style: .default, handler: { _ in
self.happyAlbum() }))
choice.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
somewhere?.present(choice, animated: false, completion: nil)
When the action sheet appears (note that present#animated is false) it just clicks on to the screen, no cheesey animation.
However, when the user taps one of the three choices, or, taps "off", the action sheet leaves the screen by using the cheesey animation.
(In 10.3 specifically, it slides downwards off the screen.)
Is there a way to turn off that exit animation?
If you subclass UIAlertController...it doesn't work?
As DS suggests below, you could subclass UIAlertController.
However - strangely - it does nothing. Here's a test
func _test() {
let msg = SuperiorUIAlertController(
title: "Hello", message: "Hello",
preferredStyle: UIAlertControllerStyle.alert)
msg.addAction(UIAlertAction(
title: "OK", style: UIAlertActionStyle.default,
handler: nil))
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(msg, animated: false, completion: nil)
}
class SuperiorUIAlertController: UIAlertController {
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
print("You should see this! \(flag)")
self.dismiss(animated: false, completion: completion)
}
}
Indeed, the text "You should see this" never appears.
I hate to answer my own question, but as of late 2017, there is no way. Weird right?
Hope this fact helps someone.
The one more way you can Override dismiss method of viewController. If you don't wanna override other animations check animated flag value or make a flag in below method.
Make your AlertController globally
var choice = UIAlertController()
Make sure add this method in your viewController which you are presented alert
Dismiss presented alert without animation like below
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
self.choice.dismiss(animated: false, completion: nil)
}
Try subclassing UIAlertController like this:
class InstantCloseAlertController: UIAlertController {
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
UIView.setAnimationsEnabled(false)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
UIView.setAnimationsEnabled(true)
}
}

UIAlert view triggered from CustomCell Class // Swift

I have a TableViewCell populated with CustomCells. In the customCells I have a button which I want to trigger an UIAlert.
Here is my code for the button in the CustomCell Class:
#IBAction func anzahlButton(sender: UIButton) {
let alert = UIAlertController(title: "Bitte gib Deine gewünschte Anzahl an:", message: nil, preferredStyle: .Alert)
alert.addTextFieldWithConfigurationHandler {
(tf:UITextField!) in
tf.keyboardType = .NumberPad
tf.addTarget(self, action: "textChanged:", forControlEvents: .EditingChanged)
}
func handler(act:UIAlertAction!) {
let tf = alert.textFields![0] as UITextField
let addItem = "\(tf.text)"
var fixedToDoItems = ""
anzahlText.setTitle("\(addItem)", forState: .Normal)
//println("User entered \(addItem), tapped \(act.title)")
}
alert.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: nil))
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: handler))
(alert.actions[1] as UIAlertAction).enabled = false
TableViewController().presentViewController(alert, animated: true, completion: nil)
}
When I hit the button I get this alert: Warning: Attempt to present <UIAlertController: 0x7fc7f0cbc5c0> on <MyApp.TableViewController: 0x7fc7f0c965f0> whose view is not in the window hierarchy!
I deed some resaerch on the debug-alert, but couldn't find an answer that is in swift and works for me... ;-)
Any ideas?
THX
//Seb
In your line TableViewController().presentViewController, TableViewController() actually creates a new instance of the view controller TableViewController. This instance is not the one currently displayed on the screen. Which is why you get the error that the view is not part of the window hierarchy.
In order to fix that move the func anzahlButton(sender: UIButton) to the TableViewController file and then connect it to the button through cellForRowAtIndexPath function. remove the #IBAction part since we are no longer connecting the button through the interface builder.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
// Your Code
cell.button.addTarget(self, action: "anzahlButton:", forControlEvents: UIControlEvents.TouchUpInside)
}
Then change the line
TableViewController().presentViewController(alert, animated: true, completion: nil)
to
self.presentViewController(alert, animated: true, completion: nil)

Resources