10
votes

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;
    }];
}
10
try this answer, it works with a timer in a cell: stackoverflow.com/a/22999019/4833705Lance Samaria

10 Answers

10
votes

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.

3
votes

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.

3
votes

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.

3
votes

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

2
votes

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.

2
votes
myButton.multipleTouchEnabled = NO;
2
votes

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
}
1
votes

Use this code: This is bool condition

button.ismultipleTouchEnabled = false
0
votes

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

0
votes

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