Search code examples
iosuiviewuibuttonuicontroluicontrolstate

UIButton disabled state not changing immediately


I have a UIButton setup which I wanted to use as a way to tell the user something was happening instead of using a loader just for this one instance.

So for example the UIControlStateNormal has:
Background Green
Title text white
Title - Click to Send

Then UIControlStateDisabled has:
Background White
Title text green
Title - Sending

Then when the button is clicked we update the button to enabled = NO;.

Issue
The issue I notice is that when the button is changed to disabled (enabled NO) the changes to the title text, color, background are not completed until all the method calls etc are complete, so too late.

I read in some other threads to use btn layoutIfNeeded and/or btn setNeedsLayout. These are what actually force the button to be updated, but do not seem to happen until after all the other calls/methods are complete. Therefore its too late to update the button text etc.

Any ideas on how to make the button update its state and attributes straight away then complete the other tasks? I have checked to make sure its on the main thread and all the updates/calls are so that is not the issue.

EDIT
Basically when the button is tapped we update the state:

- (IBAction)sendFeedbackButtonPressed:(id)sender {
    DebugLog(@"selected: %@",self.sendFeedbackButton.selected ? @"Yes" : @"No");
    DebugLog(@"highlighted: %@",self.sendFeedbackButton.highlighted ? @"Yes" : @"No");

    dispatch_async(dispatch_get_main_queue(), ^{
        [self shouldFeedbackButtonBeEnabled:NO];
        DebugLog(@"enabled: %@",self.sendFeedbackButton.enabled ? @"Yes" : @"No");
    });

    dispatch_async(dispatch_get_main_queue(), ^{
        NSString *errorMessage = [self validateForm];

        if (errorMessage) {
            [[[UIAlertView alloc] initWithTitle:nil message:errorMessage delegate:nil cancelButtonTitle:nil otherButtonTitles:NSLocalizedString(@"Feedback Form Validation Error Ok Button", nil), nil] show];
            [self shouldFeedbackButtonBeEnabled:YES];
            return;
        }

        //TODO: send it
        // Send the form values to the server here.
        [self sendFeedbackToParse];
    });
}

-(void)setupSendFeedbackButton {
    UIColor *mainColor = [UIColor colorWithRed:41/255.0f green:128/255.0f blue:185/255.0f alpha:1];

    self.sendFeedbackButton.clipsToBounds = YES;
    self.sendFeedbackButton.layer.cornerRadius = 5.0f;
    self.sendFeedbackButton.layer.borderWidth = 2;
    self.sendFeedbackButton.layer.borderColor = mainColor.CGColor;

    // Disabled State
    [self.sendFeedbackButton setBackgroundImage:[WTNUtility imageFromColor:[UIColor whiteColor]] forState:UIControlStateDisabled | UIControlStateHighlighted];
    [self.sendFeedbackButton setTitle:NSLocalizedString(@"Feedback Form Send Feedback Button - Sending feedback disabled state", nil) forState:UIControlStateDisabled | UIControlStateHighlighted];
    [self.sendFeedbackButton setTitleColor:mainColor forState:UIControlStateDisabled | UIControlStateHighlighted];

    // Normal State
    [self.sendFeedbackButton setBackgroundImage:[WTNUtility imageFromColor:mainColor] forState:UIControlStateNormal];
    [self.sendFeedbackButton setTitle:NSLocalizedString(@"Feedback Form Send Feedback Button Title", nil) forState:UIControlStateNormal];
    [self.sendFeedbackButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}

-(void)shouldFeedbackButtonBeEnabled:(BOOL)decision {
    BOOL currentState = self.sendFeedbackButton.enabled;

    if (currentState == decision) {
        return;
    } else {
        self.sendFeedbackButton.enabled = decision;
        [self.sendFeedbackButton layoutIfNeeded];
    }
}

-(void)sendFeedbackToParse {
    DebugLog(@"%s",__PRETTY_FUNCTION__);
    DebugLog(@"button is enabled: %@",self.sendFeedbackButton.enabled ? @"Yes" : @"No");
    DebugLog(@"highlighted: %@",self.sendFeedbackButton.highlighted ? @"Yes" : @"No");
//    sleep(10);

//    self.sendFeedbackButton.enabled = YES;
}

Solution

  • You're on the right track about checking the main queue, but your additional processing has to finish before the UI gets a chance to update.

    If your code is friendly to this approach, use two dispatch blocks back to the main queue. The first would be your UI-state settings, the second, your remaining processing. This lets the UI state actually update before your additional processing complete.

    Pseudocode:

    - (IBAction) handlePress:(id)sender {
      dispatch_async(dispatch_get_main_queue(), ^{
    // Do your state update (button disabled, etc)
                            });
    
      dispatch_async(dispatch_get_main_queue(), ^{
    // Do your further processing - AFTER the ui has been updated
                            });
    }