简体   繁体   中英

Ignore UIButton's touch event in a long task time

I want to ignore all touch events of a button while a long task is running.

- (void)buttonAction{
    NSLog(@"click!");
    button.enabled = NO;
    [self longTask];
}

- (void)longTask{
    NSLog(@"task begin!");
    sleep(5);
    NSLog(@"task finished!");
    button.enabled = YES;
}

During the longTask time, I click the button again, it really nothing happens. BUT, when the longTask is finished, it automatically respond to the click events and execute the longTask again! How many times I clicked when the button's enabled value is 'NO', the longTask will perform how many times.

2013-08-20 09:24:49.478 AppName[2518:c07] click!
2013-08-20 09:24:49.479 AppName[2518:c07] task begin!
2013-08-20 09:24:54.481 AppName[2518:c07] task finished!
2013-08-20 09:24:54.482 AppName[2518:c07] click!
2013-08-20 09:24:54.482 AppName[2518:c07] task begin!
2013-08-20 09:24:59.484 AppName[2518:c07] task finished!

I tried to set userInteractionEnabled=NO but got the same result.

How can make it ignores all touch events when a long task is running and never performs the task? In other words, only executes the longTask when the button is clicked at it's enabled value is 'YES'?

Thanks any help!

The sleep is simply freezing the main thread, who is responsible for all UI interations.

You should perform all long tasks in background, which can be easlily achieved with GCD . Just do like below and you should be able to achieve what you want:

- (void)buttonAction{

    NSLog(@"click!");
    button.enabled = NO;

    dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
        //Background Thread

        [self longTask];

        dispatch_async(dispatch_get_main_queue(), ^(void){
             button.enabled = YES;
        });
    });
}

- (void)longTask{
    NSLog(@"task begin!");
    [NSThread sleepForTimeInterval:5];
    NSLog(@"task finished!");

} 

Notice that when you do this way, all your UI won't be blocked anymore, just the desired button will be disabled.

As @Erik Godard mentioned, you really should consider to use some kind of UI feedback when performing this kind of tasks. You can start some process indicator in the same area where you set the button's enabled property to NO and stop it when setting the property to YES

Another way to accomplish this, without GCD, is changing the sleep by NSRunLoop 's method runUntilDate . In this way, your main thread won't be blocked too, and you would be able to achieve what you are wanting.

- (void)buttonAction{

    NSLog(@"click!");
    self.addCartButton.enabled = NO;
   [self longTask];
}

- (void)longTask{
    NSLog(@"task begin!");
    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
    NSLog(@"task finished!");
   self.addCartButton.enabled = YES;
} 

Both approach tested and seems to be working.

There are several issues here.

  1. First of all, if you are performing a task and you don't want the user to be able to enter input while it's occurring, you should provide some sort of visual indicator to tell the user that the app has not frozen up. Simply disabling a button without giving some indication of what's going on is bad for the user experience. For example, you could use a progress bar or a progress indicator to alert the user that a task is being completed.
  2. Secondly, if you're doing some task that will take a significant amount of time like the one that you're proposing, you should consider using multithreading. See here for an introduction to this. Generally, you don't want to do computationally intensive tasks on the main thread like this.
  3. Finally, if you still want to continue with this approach, you can declare a flag that defines whether or not your longTask should be called. You could make a private instance variable

     BOOL shouldCall; 

When you call longTask for the first time, set it to NO. Once the longTask completes, change it to yes again. You can use this in conjunction with .hidden to control the UI of your button.

As @LucasEduardo pointed out, the workaround in three won't work unless you get rid of the sleep, so disregard that.

As @Lucas Eduardo mention, perform a long task in background thread is the best implementation. But in some situations, the task must be performed at main thread, such as rending a PDF or a web view or moving a big image. And you still need to ignore the touch events in the runtime.

Thanks @Erik Godard's points. When I have to perform a long task in main thread, I have to set a BOOL value to response to the touch events.

- (void)buttonAction{
    NSLog(@"click!");
    [self longTask];
}

- (void)longTask{
    if (!isRunning) {
        button.enabled = NO;
        isRunning = YES;
        NSLog(@"task begin!");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"task finished!");
        button.enabled = YES;
        [self performSelector:@selector(switchStatus) withObject:nil afterDelay:.5f];
    }
}

- (void)switchStatus{
    isRunning = NO;
}
- (void)viewDidLoad
{
    [super viewDidLoad];
    isRunning = NO;
}

I change the BOOL value after 0.5 second delay, because if isRunning = NO is immediately set after button.enabled = YES, the longTask will be performed again. Just leave 0.5 second delay to let the touch events go through. At that period, isRunning's value still equal YES, so touch events will be ignored. 0.5 second later, isRunning=NO, the button will trigger new longTask if which be clicked again.

2013-08-20 20:44:19.727 AppName[4005:c07] click!
2013-08-20 20:44:19.728 AppName[4005:c07] task begin!
2013-08-20 20:44:24.730 AppName[4005:c07] task finished!
2013-08-20 20:44:24.731 AppName[4005:c07] click!
2013-08-20 20:44:24.732 AppName[4005:c07] click!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM