By Greg Maletic


2009-11-23 18:19:23 8 Comments

After my user clicks a button, I'd like that button to stay pushed during the time that I perform a network operation. When the network operation is complete, I want the button to return to its default state.

I've tried calling -[UIButton setSelected:YES] right after the button push (with a corresponding call to -[UIButton setSelected:NO] after my network op finishes) but it doesn't seem to do anything. Same thing if I call setHighlighted:.

I suppose I could try swapping out the background image to denote a selected state for the duration of the network op, but that seems like a hack. Any better suggestions?

Here's what my code looks like:

- (IBAction)checkInButtonPushed
{
    self.checkInButton.enabled = NO;
    self.checkInButton.selected = YES;
    self.checkInButton.highlighted = YES;
    [self.checkInActivityIndicatorView startAnimating];
    [CheckInOperation startWithPlace:self.place delegate:self];
}

- (void)checkInCompletedWithNewFeedItem:(FeedItem*)newFeedItem wasNewPlace:(BOOL)newPlace possibleError:(NSError*)error;
{
    [self.checkInActivityIndicatorView stopAnimating];
    self.checkInButton.enabled = YES;
    self.checkInButton.selected = NO;
    self.checkInButton.highlighted = NO;
}

9 comments

@Eike 2016-07-16 12:19:35

In swift I'm doing it like the following.

I create a Subclass of UIButton and implemented a custom property state

class ActiveButton: UIButton {

    private var _active = false
    var active:Bool {
        set{
            _active = newValue
            updateState()
        }
        get{
            return _active
        }
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.addTarget(self, action: #selector(ActiveButton.touchUpInside(_:)), forControlEvents: .TouchUpInside)
    }

    func touchUpInside(sender:UIButton) {
        active = !active
    }

    private func updateState() {
        NSOperationQueue.mainQueue().addOperationWithBlock {
            self.highlighted = self.active
        }
    }

}

Works perfectly for me.

@Bryan Henry 2009-11-23 18:30:01

How are you setting the images for the different UIControlStates on the button? Are you setting a background image for UIControlStateHighlighted as well as UIControlStateSelected?

UIImage *someImage = [UIImage imageNamed:@"SomeResource.png"];
[button setBackgroundImage:someImage forState:UIControlStateHighlighted];
[button setBackgroundImage:someImage forState:UIControlStateSelected];

If you're setting the selected state on the button touch down event rather than touch up inside, your button will actually be in a highlighted+selected state, so you'll want to set that too.

[button setBackgroundImage:someImage forState:(UIControlStateHighlighted|UIControlStateSelected)];

Edit:

To sum up my remarks in the comments and to address the code you posted...you need to set your background images for the full UIControl state that you're in. According to your code snippet, this control state would be disabled + selected + highlighted for the duration of the network operation. This means that you would need to do this:

[button setBackgroundImage:someImage forState:(UIControlStateDisabled|UIControlStateHighlighted|UIControlStateSelected)];

If you remove the highlighted = YES, then you would need this:

[button setBackgroundImage:someImage forState:(UIControlStateDisabled|UIControlStateSelected)];

Get the picture?

@Greg Maletic 2009-11-23 19:02:52

In effect, I believe I'm doing what you suggest; I'm just doing it through Interface Builder. I added the code I'm using to my question above...perhaps that would shed some light on the problem? The behavior I want is that I want the button to stay selected through the duration of my network operation. What I'm seeing is that the button highlights when touched, then the highlight goes away when the touch is done. The button -never- gets selected. Why this wouldn't work makes no sense to me, so I feel like I'm doing something stupid. I've checked all my IB connections and they're good...

@Bryan Henry 2009-11-23 19:10:04

I don't believe you can set a background image for the highlighted+selected state in Interface Builder, only the highlighted and selected states separately. If your button was in a state such that highlighted and selected were both set, I believe it would default to your normal image, not either of the highlighted or selected images. From your code, you're setting the state of the button such that both selected and highlighted are YES. Is checkInButtonPushed connected to "Touch Up Inside" or something else?

@Greg Maletic 2009-11-23 19:26:36

Yes, it is connected to "Touch Up Inside". And if I remove the code related to the 'highlighted' state, it still doesn't work. The 'selected' state never gets shown.

@Bryan Henry 2009-11-23 19:36:43

Also, why are you disabling the button? That would actually mean that the button state is disabled+highlighted+selected. If you removed the line setting highlighted to YES, then you have disabled+selected, so you need to set an image for the (UIControlStateDisabled|UIControlStateSelected) state.

@Greg Maletic 2009-11-23 19:48:04

Okay, that was the problem. When I took out the disable functionality, it worked as expected. Thanks! (To answer your question, I'm disabling the button because I don't want anyone pushing it again while the network operation is going on.)

@Bryan Henry 2009-11-23 20:04:22

You can certainly keep disabling the button, and you can even keep setting highlighted to YES as well - you just need to make sure that you set a background image for the proper control state of the button if you want to do so.

@t9mike 2013-11-24 02:36:17

Here is a C# / MonoTouch (Xamarin.iOS) implementation using approaches presented above. It assumes you have set the Highlighted image state already, and configures the selected and selected|highlighted states to use the same image.

var selected = button.BackgroundImageForState(UIControlState.Highlighted);
button.SetBackgroundImage(selected, UIControlState.Selected);
button.SetBackgroundImage(selected, UIControlState.Selected | UIControlState.Highlighted);
button.TouchUpInside += delegate
{
    NSTimer.CreateScheduledTimer(TimeSpan.FromMilliseconds(0), delegate
    {
        button.Highlighted = true;
        NSTimer.CreateScheduledTimer(TimeSpan.FromMilliseconds(200), delegate
        {
            button.Highlighted = false;
        });
    });
};

@Bob Spryn 2012-04-05 23:49:20

Use a block so you don't have to build a whole separate method:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0), dispatch_get_main_queue(), ^{
    theButton.highlighted = YES;
});

Update

To be clear you still need to set the background (or normal image) for the combination states as well as the regular ones like sbrocket says in the accepted answer. At some point your button will be both selected and highlighted, and you won't have an image for that unless you do something like this:

[button setBackgroundImage:someImage forState (UIControlStateHighlighted|UIControlStateSelected)];

Otherwise your button can fall back to the UIControlStateNormal image for the brief selected+highlighted state and you'll see a flash.

@Jacob Relkin 2012-09-14 00:39:41

FYI dispatch_get_current_queue() is stated as being "unreliable".

@Bob Spryn 2012-09-14 03:38:04

Where is that stated? What's the alternative?

@Ilya 2015-07-27 22:17:44

Is there significance in using dispatch_after as opposed to dispatch_async?

@Bob Spryn 2015-07-28 02:16:43

Yep. dispatch_async is a better choice.

@roberto.buratti 2012-03-31 19:24:27

Try using NSOperationQueue to achieve this. Try out code as follows:

[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    theButton.highlighted = YES;
}];

Hope this helps.

@Scott Lieberman 2013-01-08 07:50:12

This is a great solution, Roberto & Parth. One small detail, though, is that you will occasionally see a "flicker" if the user holds his finger down on the button. Is there any way to prevent the flicker?

@roberto.buratti 2013-02-14 20:29:56

try using the event "Touch Up Inside" instead of "Touch Down"...

@Pach 2012-03-30 14:39:23

I have another way ...if you don't want to use images, and you want the effect of a pressed button, You can subclass the Button and here's my code:

in the .h File:

@interface reservasButton : UIButton {

BOOL isPressed;
}
 @end

In the .m File:

#import <QuartzCore/QuartzCore.h>


 @implementation reservasButton

 -(void)setupView {  //This is for Shadow

    self.layer.shadowColor = [UIColor blackColor].CGColor;
    self.layer.shadowOpacity = 0.5; 
    self.layer.shadowRadius = 1;    
    self.layer.shadowOffset = CGSizeMake(2.0f, 2.0f); //comment
    //   self.layer.borderWidth = 1;
    self.contentVerticalAlignment   = UIControlContentVerticalAlignmentCenter;
    self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

    // [self setBackgroundColor:[UIColor whiteColor]];

    //  self.opaque = YES;


}

-(id)initWithFrame:(CGRect)frame{
    if((self = [super initWithFrame:frame])){
        [self setupView];
    }

    return self;
}

-(id)initWithCoder:(NSCoder *)aDecoder{
    if((self = [super initWithCoder:aDecoder])){
        [self setupView];
    }

    return self;
}

//Here is the important code

 -(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{


  if (isPressed == FALSE) {

      self.contentEdgeInsets = UIEdgeInsetsMake(1.0,1.0,-1.0,-1.0);
      self.layer.shadowOffset = CGSizeMake(1.0f, 1.0f);
      self.layer.shadowOpacity = 0.8;

      [super touchesBegan:touches withEvent:event];

      isPressed = TRUE;

     }
     else {

         self.contentEdgeInsets = UIEdgeInsetsMake(0.0,0.0,0.0,0.0);
         self.layer.shadowOffset = CGSizeMake(2.0f, 2.0f);
         self.layer.shadowOpacity = 0.5;

         [super touchesEnded:touches withEvent:event];

         isPressed = FALSE;

     }


 } `

@Hlung 2011-09-09 06:24:57

I have an easier way. Just use "performSelector" with 0 delay to perform [button setHighlighted:YES] . This will perform re-highlighting after the current runloop ends.

- (IBAction)buttonSelected:(UIButton*)sender {
    NSLog(@"selected %@",sender.titleLabel.text);
    [self performSelector:@selector(doHighlight:) withObject:sender afterDelay:0];
}

- (void)doHighlight:(UIButton*)b {
    [b setHighlighted:YES];
}

@18446744073709551615 2011-10-20 17:20:14

"Everything gets better when you turn power on"

    button.selected = !button.selected;

works perfectly... after I connected the outlet to the button in the Interface Builder.

You do not need to setBackgroundImage:forState:, the builder allows you to specify the background (gets resized if necessary) or/and foreground (not resizing) images.

@fredrik 2011-08-11 22:52:59

I had a similar problem where I wanted a button to keep it's highlight after click. The problem is if you try to use setHighlighted:YES inside of you click action it will reset right after you click action, - (IBAction)checkInButtonPushed

I solved this by using a NSTimer like this

NSTimer *timer;
timer = [NSTimer scheduledTimerWithTimeInterval: 0.01
                                         target: self
                                       selector: @selector(selectCurrentIndex)
                                       userInfo: nil
                                        repeats: NO];

and then call setHighlighted:YES from my selectCurrentIndex method. I use regular UIButtonTypeRoundedRect buttons.

Related Questions

Sponsored Content

15 Answered Questions

[SOLVED] Color Tint UIButton Image

34 Answered Questions

[SOLVED] How do I create a basic UIButton programmatically?

5 Answered Questions

[SOLVED] UIButton remove all target-actions

  • 2010-07-27 04:50:14
  • SK9
  • 69373 View
  • 321 Score
  • 5 Answer
  • Tags:   ios uibutton

13 Answered Questions

[SOLVED] UIButton: set image for selected-highlighted state

6 Answered Questions

[SOLVED] how to programmatically fake a touch event to a UIButton?

10 Answered Questions

[SOLVED] How to set the title of UIButton as left alignment?

  • 2010-05-04 11:56:19
  • Madan Mohan
  • 189067 View
  • 434 Score
  • 10 Answer
  • Tags:   objective-c uibutton

3 Answered Questions

[SOLVED] UIButton titleColor upon selection and highlight

1 Answered Questions

[SOLVED] UIButton with both Touch Up Inside and Touch Repeat?

3 Answered Questions

[SOLVED] UIButton - Image in Default State, Title in Selected State

1 Answered Questions

[SOLVED] UIButton on horizontal UIScrollView issues

Sponsored Content