开发者

Can I use custom values of UIControlState for my own control?

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 your customState
  • Override the enabled, selected and highlighted setters so that they call [self stateWasUpdated]. This will allow you to respond to any changes in state, not just changes to customState
  • 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
            }
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜