はじめに
UIButtonってあるじゃないですか。
タップなどのイベントを取れるUIKitの部品です。
これのラベルを変更させるときによくする間違いで、title
プロパティを直接代入させようとしても変更されないんです。
let btn = UIButton(frame: CGRectZero)
btn.titleLabel?.text = "not change!"
なぜなら、UIButtonにはstate
というUIControlState
のプロパティがあって、状態によって値を変えられるからです。直接の代入だと状態の指定ができないので読み取り専用プロパティで代入できないようになっているみたいです。
UIControlStateは通常状態のNormal
、タップ中を表すHighlighted
、選択中の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 }
}
この状態はこの値ということを設定するためにUIButtonには- setTitle:forState:
というメソッドが用意されています。
これでタイトルを変更します。
btn.setTitle("yes change!", forState: .Normal)
ここからが本題です。
いろいろな状態があったとしても、プロパティで設定したほうが便利じゃないかと思ったのです。
UIButtonのようにわざわざメソッドを用意するよりは、プロパティで設定する方がスマートじゃないかと思いました。
なので今回は、状態によって変化する値をプロパティで設定できるようにする方法を作っていきたいと思います。
enumのAssociated Valueを使う
Swiftのenumが使えると思いました。
Swiftのenumの定義の1つにAssociated Valueというものがあり、ある状態とそのときの値を定義することができます。
今回は、カスタムビューを作って、ジェスチャーイベントによって、背景色を変更するコードを作っていきたいと思います。
InteractionStyleViewというカスタムビューを作ります。
カスタムビューにInteractionStyle
というenumを定義します。
タップしたとき、ロングタップしたとき、スワイプしたとき、何もしないときの状態と値を以下のように定義します。
class InteractionStyleView: UIView {
enum InteractionStyle {
case Tap(UIColor)
case LongTap(UIColor)
case Swipe(UIColor)
case None(UIColor)
}
}
次に各ジェスチャーに対応するUIColor
インスタンスをプロパティで保持します。
var tapColor = UIColor.whiteColor()
var longColor = UIColor.whiteColor()
var swipeColor = UIColor.whiteColor()
var noneColor = UIColor.whiteColor()
続いて、プロパティとしてInteractionStyleを保持します。
この時に、didSet
で代入されたときに各ジェスチャーの値を取得し、それぞれ対応するUIColor
プロパティに代入します。
var interactionBackgroundColor: InteractionStyle = InteractionStyle.None(UIColor.whiteColor()) {
didSet{
switch interactionBackgroundColor {
case .Tap(let color):
tapColor = color
case .LongTap(let color):
longColor = color
case .Swipe(let color):
swipeColor = color
case .None(let color):
noneColor = color
}
}
}
最後にawakeFromNib
メソッドにUITapGestureRecognizer
、UILongPressGestureRecognizer
、UISwipeGestureRecognizer
を作成して、ジェスチャーが呼ばれた際には対応するUIColor
をbackgroundColor
プロパティにセットします。
override func awakeFromNib() {
self.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(InteractionStyleView.tapAction(_:)))
self.addGestureRecognizer(tapGesture)
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(InteractionStyleView.longAction(_:)))
self.addGestureRecognizer(longGesture)
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(InteractionStyleView.swipeAction(_:)))
self.addGestureRecognizer(swipeGesture)
}
func tapAction(sender: UITapGestureRecognizer){
backgroundColor = tapColor
}
func longAction(sender: UILongPressGestureRecognizer){
backgroundColor = longColor
}
func swipeAction(sender: UISwipeGestureRecognizer){
backgroundColor = swipeColor
}
コード全体では以下のとおりになります。
class InteractionStyleView: UIView {
enum InteractionStyle {
case Tap(UIColor)
case LongTap(UIColor)
case Swipe(UIColor)
case None(UIColor)
}
var tapColor = UIColor.whiteColor()
var longColor = UIColor.whiteColor()
var swipeColor = UIColor.whiteColor()
var noneColor = UIColor.whiteColor()
var interactionBackgroundColor: InteractionStyle = InteractionStyle.None(UIColor.whiteColor()) {
didSet{
switch interactionBackgroundColor {
case .Tap(let color):
tapColor = color
case .LongTap(let color):
longColor = color
case .Swipe(let color):
swipeColor = color
case .None(let color):
noneColor = color
}
}
}
override func awakeFromNib() {
self.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(InteractionStyleView.tapAction(_:)))
self.addGestureRecognizer(tapGesture)
let longGesture = UILongPressGestureRecognizer(target: self, action: #selector(InteractionStyleView.longAction(_:)))
self.addGestureRecognizer(longGesture)
let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(InteractionStyleView.swipeAction(_:)))
self.addGestureRecognizer(swipeGesture)
}
func tapAction(sender: UITapGestureRecognizer){
backgroundColor = tapColor
}
func longAction(sender: UILongPressGestureRecognizer){
backgroundColor = longColor
}
func swipeAction(sender: UISwipeGestureRecognizer){
backgroundColor = swipeColor
}
}
InteractionStyleViewの呼び出し
StoryboardでInteractionStyleViewを配置してUViewControllerにIBOutletで接続します。
ViewControllerからは、interactionStyleView
のinteractionBackgroundColor
プロパティを代入すればよくなります。
class ViewController: UIViewController {
@IBOutlet weak var interactionStyleView: InteractionStyleView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
interactionStyleView.interactionBackgroundColor = InteractionStyleView.InteractionStyle.Tap(UIColor.blackColor())
interactionStyleView.interactionBackgroundColor = InteractionStyleView.InteractionStyle.LongTap(UIColor.redColor())
interactionStyleView.interactionBackgroundColor = InteractionStyleView.InteractionStyle.Swipe(UIColor.yellowColor())
}
}
これを実行するとタップは黒、長押しは赤、スワイプは黄色にビューが変わるようになります。
このコードの利点
- 使う方(ViewController)はプロパティ代入すればよくなるからわかりやすくなる
欠点
- 定義する方(InteractionStyleView)では各状態のプロパティをそれぞれ保持するのでコード量が多くなる?
終わりに
プロパティ代入で状態と値の両方を設定する方法を見ていきました。
何かの訳に立てれば幸いです。
また、もっとやりようがありそうですが、今のところ思いつかなかったです。
もっといい方法があればコメントください。