Is there a way to set a custom states -- not one of the existing UIControlState
values -- for a UIControl
?
In the UIControlSate
enum, there are 16 bits that can be used for custom control states:
UIControlStateApplication = 0x00FF0000, // additional flags available for application use
The problem is that UIControl
's state
pr开发者_如何学运维operty is readonly.
I want to set different background images to my UIButton
for custom states.
You can make use of the custom states in a subclass of UIControl.
- Create a variable called
customState
in which you will manage your custom states. - If you need to set a state, do your flag operations against this variable, and call
[self stateWasUpdated]
. - Override the
state
property to return[super state]
bitwise OR'd against yourcustomState
- Override the
enabled
,selected
andhighlighted
setters so that they call[self stateWasUpdated]
. This will allow you to respond to any changes in state, not just changes tocustomState
- Implement
stateWasUpdated
with logic to respond to changes in state
In the header:
#define kUIControlStateCustomState (1 << 16)
@interface MyControl : UIControl {
UIControlState customState;
}
In the implementation:
@implementation MyControl
-(void)setCustomState {
customState |= kUIControlStateCustomState;
[self stateWasUpdated];
}
-(void)unsetCustomState {
customState &= ~kUIControlStateCustomState;
[self stateWasUpdated];
}
- (UIControlState)state {
return [super state] | customState;
}
- (void)setSelected:(BOOL)newSelected {
[super setSelected:newSelected];
[self stateWasUpdated];
}
- (void)setHighlighted:(BOOL)newHighlighted {
[super setHighlighted:newHighlighted];
[self stateWasUpdated];
}
- (void)setEnabled:(BOOL)newEnabled {
[super setEnabled:newEnabled];
[self stateWasUpdated];
}
- (void)stateWasUpdated {
// Add your custom code here to respond to the change in state
}
@end
Based on @Nick answer I have implemented a simpler version. This subclass exposes a BOOL outlined
property that is similar in function to selected
, highlighted
and enabled
.
Doing things like [customButtton setImage:[UIImage imageNamed:@"MyOutlinedButton.png"] forState:UIControlStateOutlined]
makes it automagically work when you update the outlined
property.
More of these state + property could be added if needed.
UICustomButton.h
extern const UIControlState UIControlStateOutlined;
@interface UICustomButton : UIButton
@property (nonatomic) BOOL outlined;
@end
UICustomButton.m
const UIControlState UIControlStateOutlined = (1 << 16);
@interface OEButton ()
@property UIControlState customState;
@end
@implementation OEButton
- (void)setOutlined:(BOOL)outlined
{
if (outlined)
{
self.customState |= UIControlStateOutlined;
}
else
{
self.customState &= ~UIControlStateOutlined;
}
[self stateWasUpdated];
}
- (BOOL)outlined
{
return ( self.customState & UIControlStateOutlined ) == UIControlStateOutlined;
}
- (UIControlState)state {
return [super state] | self.customState;
}
- (void)stateWasUpdated
{
[self setNeedsLayout];
}
// These are only needed if you have additional code on -(void)stateWasUpdated
// - (void)setSelected:(BOOL)newSelected
// {
// [super setSelected:newSelected];
// [self stateWasUpdated];
// }
//
// - (void)setHighlighted:(BOOL)newHighlighted
// {
// [super setHighlighted:newHighlighted];
// [self stateWasUpdated];
// }
//
// - (void)setEnabled:(BOOL)newEnabled
// {
// [super setEnabled:newEnabled];
// [self stateWasUpdated];
// }
@end
I'd like to offer a slight refinement to this strategy. See this stackoverflow question:
Overriding isHighlighted still changes UIControlState - why?
It turns out that Apple's state
implementation is actually a computed property based off the other properties, isSelected
, isHighlighted
, isEnabled
, etc.
So there is actually no need for a custom state bit mask on top of UIControlState (well, it's not that there is no need, it's just that it's adding complexity where there need/ought not be).
If you wanted to be congruent with Apple's implementation, you would just override the state property and check your custom states in the getter.
extension UIControlState {
static let myState = UIControlState(rawValue: 1 << 16)
}
class MyControl: UIControl {
override var state: UIControlState {
var state = super.state
if self.isMyCustomState {
state.insert(UIControlState.myState)
}
return state
}
var isMyCustomState: Bool = false
}
It's actually a smart way to go; as per the link above, if you override the property and don't change the state you will get inconsistent results. Making state always a computed property ensures consistency between the properties that state
represents.
Swift 3 version of Nick's answer:
extension UIControlState {
static let myState = UIControlState(rawValue: 1 << 16)
}
class CustomControl: UIControl {
private var _customState: UInt = 0
override var state: UIControlState {
return UIControlState(rawValue: super.state.rawValue | self._customState)
}
var isMyCustomState: Bool {
get {
return self._customState & UIControlState.myState.rawValue == UIControlState.myState.rawValue
} set {
if newValue == true {
self._customState |= UIControlState.myState.rawValue
} else {
self._customState &= ~UIControlState.myState.rawValue
}
}
}
}
精彩评论