I am trying to attach a UITapGestureRecognizer to the View of a UIAlertController but the recognize event is never firing.
I am coding this in Xamarin, but I feel like the issue applies to native code as well.
InvokeOnMainThread(() =>
var alert = new UIAlertController();
alert.Title = "My Title";
alert.Message = "My Message";
UITapGestureRecognizer tapGestureRecognizer = new
UITapGestureRecognizer((gesture) =>
{
//I never get here
});
alert.View.AddGestureRecognizer(tapGestureRecognizer);
alert.View.UserInteractionEnabled = true;
this.PresentViewController(alert, true, null);
});
Ideally, I would like to dismiss the alert when a user touches the popup, but I can't seem to detect the gesture.
I've tried adding the recognizer both, before, and after the alert is presented.
Solution:
To dismiss the UIAlertController by clicking the background View, you can add the tapGestureRecognizer to the last view in the screen when UIAlertController is popping up, check the code below:
public partial class ViewController : UIViewController
{
public ViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
var alert = new UIAlertController();
alert.Title = "My Title";
alert.Message = "My Message";
UIAlertAction ac1 = UIAlertAction.Create("123",UIAlertActionStyle.Cancel,null);
alert.AddAction(ac1);
this.PresentViewController(alert, true, addGesOnBackGround);
}
public void addGesOnBackGround() {
UIView backView = new UIView();
Array arrayViews = UIApplication.SharedApplication.KeyWindow.Subviews;
if (arrayViews.Length >0)
{
backView = arrayViews.GetValue(arrayViews.Length-1) as UIView;
}
UITapGestureRecognizer tapGestureRecognizer = new
UITapGestureRecognizer((gesture) =>
{
//I never get here
this.DismissViewControllerAsync(true);
});
backView.AddGestureRecognizer(tapGestureRecognizer);
backView.UserInteractionEnabled = true;
}
}
Related
Some times in my app I get this error because the UI freezes and the users tap more than once the buttons:
"pushing the same view controller instance more than once is not
supported"
I have tried this:
How to prevent multiple event on same UIButton in iOS?
And it works like a charm but if my tabbar has more than 5 elements if I tab the button that shows an element greater than 5 the more button animates from left to right.
Is there other way to prevent the double tab in an easy way that does not use animations?.
This is the code I'm using:
- (IBAction)btnAction:(id)sender {
UIButton *bCustom = (UIButton *)sender;
bCustom.userInteractionEnabled = NO;
[UIView animateWithDuration:1.0 delay:0.0 options:UIViewAnimationOptionAllowAnimatedContent animations:^{
[self selectTabControllerIndex:bCustom.tag];
} completion:^(BOOL finished){
bCustom.userInteractionEnabled = YES;
}];
}
First a tip, if you only have button's calling that selector, you can change the id to UIButton* and drop the extra variable bCustom.
Now, to solve your issue, you just need to ensure you turn userInteractionEnabled back to YES after you'd done whatever else you needed to do. Using the animation block is just an easy way because it has a completion handler built in.
You can do this simply by having selectTabControllerIndex method do the work for you.
Something like this:
- (IBAction)btnAction:(UIButton*)sender {
sender.userInteractionEnabled = NO;
[self selectTabControllerForButton:sender];
}
- (void)selectTabControllerForButton:(UIButton*)sender {
// Whatever selectTabControllerIndex does now goes here, use sender.tag as you used index
sender.userInteractionEnabled = YES;
}
If you possibly had other code you needed to execute afterwards, you could add a completion handler to your selectTabControllerIndex method instead and then call the completion handler. Inside that you'd include the sender.userInteractionEnabled = YES; line. But if it's always the same code, the first way is easier and faster.
Using userInteractionEnable=false to prevent double tap is like using a Rocket Launcher to kill a bee.
Instead, you can use myButton.enabled=false.Using this, you may be able to change ( if you want ) the layout of your button when it is deactivated.
In Swift, you can also use defer keyword, to execute a block of code that will be executed only when execution leaves the current scope.
#IBAction func btnAction(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
defer {
sender.isUserInteractionEnabled = true
}
// rest of your code goes here
}
Note: This will only be helpful if the "rest of your code" is not async, so that the execution actually leaves the current scope.
In async cases you'd need to set isUserInteractionEnabled = true at the end of that async method.
Disable isUserInteractionEnabled or disable the button not work some cases, if have background API calling in next controller, push process will work asynchronously.
After some work around i thought its better to go with the other way, i found Completion handler in Objective-C or Closure in Swift can be good here.
Here is the example which i used in Objective c:
-(void)didSettingClick:(id) sender
{
if (!isPushInProcess) {
isPushInProcess = YES;
SettingVC *settings = [[SettingVC alloc] initWithcomplition:^{
isPushInProcess = NO;
}];
[self.navigationController pushViewController:settings animated:YES];
}
}
Here is method description:
dispatch_block_t pushComplition;
-(instancetype) initWithcomplition:(dispatch_block_t)complition{
self = [super init];
if (self) {
pushComplition = complition;
}
return self;
}
Inside viewDidAppear()
-(void)viewDidAppear:(BOOL)animated
{
pushComplition();
}
In swift using defer keyword is also can be good idea.
Hope It help!!!
You can disable the userInteraction for that button when user taps for first time.
Then new view controller will appear, while leaving to new View Controller call this
-(IBAction)btnAction:(UIButton *)sender {
sender.userInteractionEnabled=NO;
//do your code
}
if it is moving to another view then call below one
-(void)viewWillDisappear {
buttonName.userInteractionEnabled=YES;
}
if not moving from present view
you can call
sender.userInteractionEnabled=YES;
at the end of btnAction method.
It will work for sure.
myButton.multipleTouchEnabled = NO;
Swift 4 version of #Santo answer that worked for me:
Button code:
#IBAction func btnMapTap(_ sender: UIButton) {
sender.isUserInteractionEnabled = false
//put here your code
Add override method viewWillDisappear:
override func viewWillDisappear(_ animated: Bool) {
btnMap.isUserInteractionEnabled = true
}
Use this code: This is bool condition
button.ismultipleTouchEnabled = false
it seems that under iOS 14.x it will happen automatically when You tap.
I have written small demo app with a nav controller, a controller of class "ViewController" with a button invoking an action "pushIt".
(see code)
I have set Storyboard ID to a separated controller to "ColoredVCID" and added a global counter, just to see...
Long way SHORT: it seems working correctly.
// compulsiveTouch
//
// Created by ing.conti on 03/08/21.
import UIKit
fileprivate var cont = 0
class ViewController: UIViewController {
#IBAction func pushIt(_ sender: Any) {
cont+=1
print(cont)
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "ColoredVCID")
self.navigationController!.present(vc, animated: true)
// OR:
self.navigationController!.pushViewController(vc, animated: true)
}
}
In PAST days I usually did:
#objc func pushItOLD(_sender: Any){
// prevent compulsive touch:
self.setButtonActive(btn: self.pushBtn!, active: false)
// now re-eanble it... after 1 second:
let when = DispatchTime.now() + 1
DispatchQueue.main.asyncAfter(deadline: when, execute: { () -> Void in
self.setButtonActive(btn: self.pushBtn!, active: true)
})
}
func setButtonActive(btn: UIButton?, active: Bool){
guard let btn = btn else{
return
}
btn.isEnabled = active
btn.alpha = (active ? 1 : 0.5)
}
that CAN BE very useful nowadays if your button for example invokes a network request... to prevent double calls.
(I added some cosmetics to use alpha.. to let user see it as "disabled" ..)
I did it like this
var callInProgress = false
func call(){
if callInProgress == true{
return
}
callInProgress = true
//Make it false when your task done
}
it will not allow user to call the function one more time untill you make callInProgress false
This is the only thing working
I have a UIView, let's say MyView which contains a UILabel, UISegmentControl and a UIButton.
So
MyView.Hidden = false;
and
MyView.Tag = 1000;
to make sure it doesn't touch it.
now when I click outside that view I want it to hide, to that end I have some code which adds tap recognisers, ignores MyView (I hoped) and hides the view when you click outside. It removes the tab recognisers until MyView shows up again. It works, however when I click on the subviews of MyView (but NOT MyView itself) it calls ViewTap as well and thus disappears it as well. I cannot find why this happens...
class ViewTap : OutsideEditTap
{
public ViewTap(UIView MainView) {
this.AddTarget(() => {
MyView.Hidden = true;
RemoveTapGesture(this.View);
});
CancelsTouchesInView = false;
}
}
class OutsideEditTap : UITapGestureRecognizer
{
}
public static void RemoveTapGesture(UIView view) {
if (view.GestureRecognizers != null) for (int i = 0; i < view.GestureRecognizers.Length; i++) {
if (view.GestureRecognizers[i].IsSubClassOf(typeof(OutsideEditTap))) {
view.RemoveGestureRecognizer (view.GestureRecognizers [i]);
}
}
foreach (UIView subView in view.Subviews) {
RemoveTapGesture (subView);
}
}
public static void AddTapGesture(UIView view, UIView MainView) {
OutsideEditTap tap = null;
if (view.Tag != 1000) { // skip MyView
tap = new ViewTap (MainView);
view.AddGestureRecognizer (tap);
foreach (UIView subView in view.Subviews) {
AddTapGesture (subView, MainView);
}
}
}
I am sure this is probably not the best method (I don't think the whole idea was not the best implementation to begin with, but no-one provided any alternatives unfortunately); the solution I used to solve this was to add to the AddTarget Action:
var p = this.LocationOfTouch(0, this.View);
if (MyView.Frame.Contains(p)) { return; }
which works well. I would still like to hear a better way of doing this.
I have this simple code for camera View controller:
UIImagePickerController picker = new UIImagePickerController();
picker.PrefersStatusBarHidden ();
picker.SourceType = UIImagePickerControllerSourceType.Camera;
UIImagePickerControllerCameraDevice dev = picker.CameraDevice;
PresentViewController (picker, false, null);
picker.FinishedPickingMedia += (object sender, UIImagePickerMediaPickedEventArgs e) => BeginInvokeOnMainThread (delegate {DismissViewController (false, null);});
When app starts, I can capture photo normally, but when i present picker again, camera View appears but frame(image) from previous shot is shown and frozen. If i move my device around image doesn't change. In other words, I can use camera once but I can not use it twice. What I am doing wrong? On iOS6 devices it works perfectly.
Making a pickerDelegate class did the trick for me. You just have to pass the current VC in the constructor so you can handle the image in your VC.
PickerDelegate
private class pickerDelegate : UIImagePickerControllerDelegate
{
private yourVC _vc;
public pickerDelegate (yourVC controller) : base ()
{
_vc = controller;
}
public override void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo)
{
//Do something whit the image
_vc.someButton.SetBackgroundImage (image, UIControlState.Normal);
//Dismiss the pickerVC
picker.DismissViewController (true, null);
}
}
ViewDidLoad
imagePicker = new UIImagePickerController ();
//Set the Delegate and pass the current VC
imagePicker.Delegate = new pickerDelegate (this);
I use to develop an application MonoTouch Iphone, but I have a problem using UIPopoverController. I can not open the page to select the photo.
I use the class of camera.cs TweetStation.
Here's the code:
public static void SelectPicture (UIViewController parent, Action<NSDictionary> callback)
{
if(OzytisUtils.isIpad()){
picker = new UIImagePickerController();
UIPopoverController popover = new UIPopoverController(picker);
picker.Delegate = new CameraDelegate();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
popover.SetPopoverContentSize(new SizeF(parent.View.Frame.Width,parent.View.Frame.Height),true);
if(popover.PopoverVisible){
popover.Dismiss(true);
picker.Dispose();
popover.Dispose();
}else{
popover.PresentFromRect(parent.View.Frame,parent.View,UIPopoverArrowDirection.Right,true);
}
}else{
Init ();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
_callback = callback;
parent.PresentModalViewController (picker, true);
}
Thanks for your help.
I have a few suggestions. First make the UIPopoverController a member variable so that it does not get collected.
Second I called ContentSizeForViewInPopover on the picker.
picker.ContentSizeForViewInPopover = new SizeF(this.View.Frame.Width,this.View.Frame.Height);
Finally I use a 0x0 rectangle in the upper left of the screen for the PresentFromRect call.
_popover.PresentFromRect(new RectangleF (0,0,0,0),this.View,UIPopoverArrowDirection.Up,true);
What I want to do is create a Popup with a UIImagePickerController. This parts easy but I want to create a utility method that generates the UIImagePickerController popup and returns the UIImage once the user selects it. The problem is that the UIImagePickerController has a delegate property that is used for asynchronous completion. My thought was that maybe I could pass in a delegate to my utility function that contains the code to execute once the image is selected but the code to execute needs to operate on the image that was selected. This is the code I have so far and just so everyone knows, it crashes. I believe it's because I'm executing it in a static method.
namespace GalleryProto
{
static public class CameraUtility
{
public static void GetImageFromGalleryWithPopup(UIViewController parentViewController, PointF centerPoint)
{
UIImagePickerController imagePicker;
UIPopoverController popOver;
imagePicker = new UIImagePickerController ();
popOver = new UIPopoverController (imagePicker);
popOver.DidDismiss += (popOverController, e) =>
{
if (popOver != null && popOver.PopoverVisible) {
Console.WriteLine ("Popover Dismissed.");
popOver.Dismiss (true);
imagePicker.Dispose ();
popOver.Dispose ();
imagePicker = null;
popOver = null;
}
};
Console.WriteLine ("Before Finished Picking Image Delegate.");
imagePicker.Delegate = new MyPickerDelegate (imagePicker, popOver);
imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
imagePicker.AllowsEditing = false;
imagePicker.MediaTypes = new string[] {"public.image"};
RectangleF popRectangle = new RectangleF (centerPoint, new SizeF (1, 1));
popOver.PresentFromRect (popRectangle, parentViewController.View, 0, true); //Center the popup on the Image Content View.
}
public class MyPickerDelegate : UIImagePickerControllerDelegate
{
UIImagePickerController _imagePicker;
UIPopoverController _popOver;
public MyPickerDelegate(UIImagePickerController imagePicker, UIPopoverController popOver)
{
_imagePicker = imagePicker;
_popOver = popOver;
}
public override void Canceled (UIImagePickerController picker)
{
Console.WriteLine("Canceleled");
}
public override void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo)
{
Console.WriteLine("Finished Picking Image");
_popOver.Dismiss (true);
_imagePicker.Dispose ();
_popOver.Dispose ();
_imagePicker = null;
_popOver = null;
}
}
}
}
I solved my issue by passing in a delegate for the callback that takes a UIImage as an argument so that the image can be manipulated appropriately once it's selected.