我可以使用自定义的UIControlState值来控制我的自定义控件吗?

30

有没有一种方法可以为UIControl设置自定义状态,而不是现有的UIControlState值之一?

UIControlSate枚举中,有16位可以用于自定义控件状态:

UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
问题在于UIControlstate属性是只读的。我想为自定义状态设置不同的背景图像到我的UIButton
4个回答

37
你可以在UIControl的子类中使用自定义状态。
  • 创建一个名为customState 的变量来管理自定义状态。
  • 如果需要设置状态,请对此变量进行标志操作,并调用[self stateWasUpdated]
  • 覆盖state属性,使其返回[super state]按位或运算与你的customState
  • 重写enabledselectedhighlightedsetter方法,以便它们调用[self stateWasUpdated]。这将允许你响应任何状态的更改,而不仅仅是customState的更改。
  • 使用逻辑实现stateWasUpdated以响应状态的更改。

在头文件中:

#define kUIControlStateCustomState (1 << 16)

@interface MyControl : UIControl {
    UIControlState customState;
}
在实现中:
@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

4
UIControlState枚举类型指定应用程序控件状态使用掩码0x00FF0000,即1<<16到1<<23。你使用了1<<3,这样做是否有效?它可能与苹果未来可能添加的控制状态发生冲突吗? - Adam Ritenauer
2
还需要注意的是:如果您计划使用自定义状态来控制UIButton中的自定义资源,例如标题、背景图像、图像、titleShadow或attributedTitle。在更改自定义状态后,必须调用setNeedsLayout。否则,按钮只会在再次被点击后更新其外观。 - Adam Ritenauer
绝对不要使用1 << 3,正如Adam所说,这将在未来的操作系统版本中发生冲突。请使用位掩码范围为0x00FF0000的数字。 - christophercotton
一个很好的支持性解释在另一个问题中:UIControlState“application”在UIButton中有什么用? - denkeni

6
基于@Nick的回答,我实现了一个更简单的版本。这个子类公开一个名为outlined的布尔属性,其功能类似于selectedhighlightedenabled
[customButtton setImage:[UIImage imageNamed:@"MyOutlinedButton.png"] forState:UIControlStateOutlined]这样的操作会在更新outlined属性时自动生效。
如果需要,可以添加更多这种状态+属性

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

3
我想对这个策略作出一些微调。请看这个stackoverflow的问题:点击此处
事实证明,苹果公司的`state`实现实际上是一个基于其他属性(例如`isSelected`、`isHighlighted`、`isEnabled`等)的计算属性。
因此,在UIControlState之上添加自定义状态位掩码其实是没有必要的(虽然并非完全没有必要,但这样做会增加不必要的复杂性)。
如果你想与苹果公司的实现保持一致,只需重写`state`属性并在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
 }

实际上这是一种聪明的方式;根据上面的链接,如果你覆盖了属性但没有改变状态,你将得到不一致的结果。将状态始终作为计算属性可以确保 state 表示的属性之间的一致性。


1
我认为将自己作为额外位注入到现有的“state”位掩码中是没有意义的。这样做非常“脆弱”。从完全不同的方式派生状态是一回事,但是假设苹果自己的“state”位的第16位始终是空闲的并且永远都会是你的黑客行为是错误的。 - matt
我同意,在某种程度上,所以我点了个赞。然而,我认为将其称为错误有点过分了。我们都是基于假设编写代码的,我认为这很可能是可以接受的(或者至少稍后通过简单的位移更改容易解决)。这个hack可行并且它为我节省了很多烦恼。例如,当根据状态分配自定义颜色时,它可以帮助你避免创建一堆if/else嵌套的情况。例如:if (isSelected && isHighlighted) && (!isEnabled || isMyCustomState)。我会感激任何关于管理此情况的替代策略的链接。 - MH175
我会创建自己的OptionSet。我没有看到这个页面上的任何策略这样做。 - matt

2

以下是Nick答案的Swift 3版本:

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
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接