新成人のみなさん、おめでとうございます。大人になるのは難しいことだけど、自由に自分の人生を歩んでほしいと思います。
UIControlState
UIControlにはvar state: UIControlState { get }というプロパティがある。これはすべてのUIControlの状態を示す。UIControlStateは同時に複数の状態を保持できるbit-maskであり、SwiftにおいてはOptionSetTypeとして表現される。つまり例えば、.Disabledでありかつ.Selectedであることが起こり得る。
struct UIControlState : OptionSetType { init(rawValue rawValue: UInt) static var Normal: UIControlState { get } static var Highlighted: UIControlState { get } static var Disabled: UIControlState { get } static var Selected: UIControlState { get } static var Focused: UIControlState { get } static var Application: UIControlState { get } static var Reserved: UIControlState { get } }
UIControlStateの様々な状態
このvar state: UIControlState { get }は、読み込みしかできない。ヘッダには以下のようにある。
could be more than one state (e.g. disabled|selected). synthesized from other flags.
すなわち、var enabled: Boolやvar selected: Bool、var highlighted: Boolといったプロパティから合成されることになっている。
新しく状態を作る
ここでUIControlState.Applicationというものがあることに気付いただろうか。ドキュメントには以下のように書かれている。
Additional control-state flags available for application use.
つまりこの.Applicationは、私たちのアプリケーションで自由に使ってよいことになっている。Objective-Cのヘッダを見ると以下のように定義されている。
UIControlStateApplication = 0x00FF0000
UIControlStateApplicationの値
Swiftでは隠蔽されてしまっているが、実際の値は0x00FF0000であり、bit-maskにおいて8 bit分の幅が用意されていることがわかる(いうまでもないが、16進数で二桁分であるから、2進数で表現すれば8桁分となる)。この範囲を自由に割り当てて、新しく状態を増やしてみよう。
一度でも押されたことが記録されるボタンを作る
実際の例を示す。
extension UIControlState { static var Visited = UIControlState(rawValue: 1 << 16) } class VisitableButton: UIButton { override var state: UIControlState { if visited { return super.state.union(.Visited) } else { return super.state } } var visited: Bool = false { didSet { setNeedsDisplay() titleLabel?.setNeedsDisplay() imageView?.setNeedsDisplay() } } }
VisitableButton
このUIButtonを継承したVisitableButtonには、新しくvar visited: Bool というプロパティを追加してある。そしてこのvisitedを元に、var state: UIControlState { get }をオーバーライドして新しい状態であるUIControlState.Visitedを追加する。(visitedが設定された後に表示を更新する必要があるだろうから、didSetでなるべく自身を更新するようにしている。)
UIControlState.Visitedは1 << 16であり、これは0x00FF0000のUIControlState.Applicationの範囲に入っている(これを確かめるにはprint(0x00FF0000 as UInt & (1 << 16) as UInt)とするか、print(UIControlState.Application.contains(.Visited))とする)。オーバーライドしているstateプロパティでは、OptionSetTypeのunionメソッドを利用して、元の値との論理和を作っている。
class ViewController: UIViewController { @IBOutlet weak var button: VisitableButton! { didSet { button.setTitleColor(UIColor(red: 0x00 / 0xFF, green: 0x00 / 0xFF, blue: 0xEE / 0xFF, alpha: 1), forState: .Normal) button.setTitleColor(UIColor(red: 0x55 / 0xFF, green: 0x1A / 0xFF, blue: 0x8B / 0xFF, alpha: 1), forState: .Visited) } } @IBAction func visit(sender: VisitableButton) { sender.visited = true } }
VisitableButtonを利用するコード例
利用する側は、単にvisitedプロパティを操作するだけでよい。この場合は事前に色を設定している。Webのリンクのように、一度でも訪れたページへのリンクが青から紫になる、というのを模している。
UIButtonのsetXxx(_:forState:)といったメソッドは、このようにUIControlState.Applicationの範囲に定義された独自の状態に対しても機能する。すなわち、事前に状態毎に設定しておくだけで、自動的にそれが反映されるのである。
まとめ
UIControlのvar state: UIControlState { get }プロパティを、新たな状態を作って拡張する方法を示した。この方法によって、UIControlの状態を統一的に表すことができる。
もし既存のコードで、UIControlやそのサブクラスに何らかの状態を増やしたというつもりで、何度もsetXxx(_:forState:)のようなメソッドを呼び出していたら、それはおおよそ間違いであるとみられる。
同様にUIControlEventsにも.ApplicationReservedという値があり、独自のイベントを作ることができる。
UIControlEventApplicationReserved = 0x0F000000
UIControlEvents.ApplicationReservedは4 bitの範囲を持つ
参考
- ios - Can I use custom values of UIControlState for my own control? - Stack Overflow
- UIControl Class Reference
- OptionSetType Protocol Reference

- 作者: 荻原剛志
- 出版社/メーカー: SBクリエイティブ
- 発売日: 2015/12/25
- メディア: 単行本
- この商品を含むブログを見る