How listen for UIButton state change?
I'm extending UIButton
with generic functionality to change certain appearance attributes based on the displayed title.
In order to do this, I need to detect and respond to changes in the "state" property. This is so I make sure the appearance is adjusted properly if the user has set different titles for different states. I assumed I would need to use some sort of KVO like the f开发者_StackOverflow社区ollowing:
[self addObserver:self
forKeyPath:@"state"
options:NSKeyValueObservingOptionNew
context:nil];
But this does not seem to fire the observeValueForKeyPath:... method for @"state" or @"currentTitle". I assume this is because UIButton does not implement the KVO pattern for those properties.
I do not want to just listen for clicks. Those events cause a state change, but are not the only potential causes.
Does anyone know a way to listen to and respond to state changes of a UIButton?
Thanks
UPDATE
Just a note since I've learned a few things in the last couple years ;).
I've since talked with some Apple folks who know, and the reason KVO doesn't work on the state property owes to the fact that NONE of UIKit is guaranteed to be KVO compliant. Thought that was worth repeating here--if you are trying to listen to any property of a UIKit framework class, be aware that it may work but is not officially supported and could break on different iOS versions.
Alright I figured out a solution that works. You can listen to the text property of the button's titleLabel.
[self.titleLabel addObserver:self
forKeyPath:@"text"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld
context:nil];
It seems to get fired twice per change, so you should check to make sure that the values of @"old" and @"new" in the passed change dictionary are different.
NOTE: Don't use @"old" and @"new" directly. The constants are NSKeyValueChangeOldKey and NSKeyValueChangeNewKey respectively.
I needed this today, so I wrote this class which does the job:
MyButton.h
#import <UIKit/UIKit.h>
// UIControlEventStateChanged uses the first bit from the UIControlEventApplicationReserved group
#define UIControlEventStateChanged (1 << 24)
@interface MyButton : UIButton
@end
MyButton.m
#import "MyButton.h"
#pragma mark - Private interface
@interface MyButton ()
- (void)checkStateChangedAndSendActions;
@end
#pragma mark - Main class
@implementation MyButton
{
// Prior state is used to compare the state before
// and after calls that are likely to change the
// state. It is an ivar rather than a local in each
// method so that if one of the methods calls another,
// the state-changed actions only get called once.
UIControlState _priorState;
}
- (void)setEnabled:(BOOL)enabled
{
_priorState = self.state;
[super setEnabled:enabled];
[self checkStateChangedAndSendActions];
}
- (void)setSelected:(BOOL)selected
{
_priorState = self.state;
[super setSelected:selected];
[self checkStateChangedAndSendActions];
}
- (void)setHighlighted:(BOOL)highlighted
{
_priorState = self.state;
[super setHighlighted:highlighted];
[self checkStateChangedAndSendActions];
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
_priorState = self.state;
[super touchesBegan:touches withEvent:event];
[self checkStateChangedAndSendActions];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
_priorState = self.state;
[super touchesMoved:touches withEvent:event];
[self checkStateChangedAndSendActions];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
_priorState = self.state;
[super touchesEnded:touches withEvent:event];
[self checkStateChangedAndSendActions];
}
- (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event
{
_priorState = self.state;
[super touchesCancelled:touches withEvent:event];
[self checkStateChangedAndSendActions];
}
#pragma mark - Private interface implementation
- (void)checkStateChangedAndSendActions
{
if(self.state != _priorState)
{
_priorState = self.state;
[self sendActionsForControlEvents:UIControlEventStateChanged];
}
}
@end
You can create it programatically using a UIButton
init
method, or use it from Interface Builder by adding a normal UIButton
to your view and changing the class to MyButton
, but you must listen for the UIControlEventStateChanged
event programatically. For example from viewDidLoad
in your controller class like this:
[self.myButton addTarget:self
action:@selector(myButtonStateChanged:)
forControlEvents:UIControlEventStateChanged];
[self addObserver:self
forKeyPath:@"state"
options:NSKeyValueObservingOptionNew
context:nil];
Works fine if you check inside observer 'selected' property
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqualToString:@"selected"])
{
[self.img setImage:self.selected ? self.activeImg : self.inactiveImg];
}
else
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
Subclass UIButton, override setState: is what works for me. This is probably not the best way, but I have done it successfully.
Apologies for the above answer, it was wrong. Should have actually looked at my code. In my case, I only needed to change the state based on highlight, so I overrode -setHighlight:
to change whatever values I needed. YMMV.
精彩评论