11

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;
    }];
}
Community
  • 1
  • 1
Stornu2
  • 2,284
  • 3
  • 27
  • 47

10 Answers10

12

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.

Dave Wood
  • 13,143
  • 2
  • 59
  • 67
6

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.

Dima G
  • 1,945
  • 18
  • 22
4

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.

CZ54
  • 5,488
  • 1
  • 24
  • 39
  • can u explain what is the problem with userInteractionEnable= false – jayant rawat Nov 16 '16 at 02:13
  • 1
    The problem is that `userInteractionEnable` is a view property, and `myButton.enabled=false` is a UIButton property. So you always should use the way Apple gives you to achieve your goal. – CZ54 Nov 16 '16 at 08:12
  • 4
    Note that using the `enabled` property has other side effects which aren't desired in a case like this. Those are UI changes, such as changing the colour, text, image that are on the button to indicate to the user the button is disabled. That means you'll get a sort of flash when the button is disabled and then enabled again shortly afterwards, which can distract/confuse the user. Also, please don't kill bees. :) – Dave Wood Mar 08 '18 at 23:26
3

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!!!

guru
  • 2,727
  • 3
  • 27
  • 39
2

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.

Santo
  • 1,611
  • 3
  • 17
  • 37
2

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
}
Anton Eregin
  • 8,140
  • 1
  • 12
  • 14
1
myButton.multipleTouchEnabled = NO;
RemembranceNN
  • 280
  • 3
  • 8
1

Use this code: This is bool condition

button.ismultipleTouchEnabled = false
Srinivasan_iOS
  • 972
  • 10
  • 12
1

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" ..)

ingconti
  • 10,876
  • 3
  • 61
  • 48
0

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