新成人のみなさん、おめでとうございます。大人になるのは難しいことだけど、自由に自分の人生を歩んでほしいと思います。
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
- メディア: 単行本
- この商品を含むブログを見る