【Swift】状態によって変化する値をスマートに実装する


## はじめに
UIButtonってあるじゃないですか。
タップなどのイベントを取れるUIKitの部品です。
これのラベルを変更させるときによくする間違いで、`title`プロパティを直接代入させようとしても変更されないんです。

```swift
let btn = UIButton(frame: CGRectZero)
btn.titleLabel?.text = "not change!"

```

なぜなら、UIButtonには`state`という`UIControlState`のプロパティがあって、状態によって値を変えられるからです。直接の代入だと状態の指定ができないので読み取り専用プロパティで代入できないようになっているみたいです。

UIControlStateは通常状態の`Normal `、タップ中を表す`Highlighted`、選択中の`Selected `などなど。

```swift
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:`というメソッドが用意されています。
これでタイトルを変更します。

```swift
btn.setTitle("yes change!", forState: .Normal)
```

ここからが本題です。
いろいろな状態があったとしても、プロパティで設定したほうが便利じゃないかと思ったのです。
UIButtonのようにわざわざメソッドを用意するよりは、プロパティで設定する方がスマートじゃないかと思いました。
なので今回は、状態によって変化する値をプロパティで設定できるようにする方法を作っていきたいと思います。


## enumのAssociated Valueを使う
Swiftのenumが使えると思いました。
Swiftのenumの定義の1つにAssociated Valueというものがあり、ある状態とそのときの値を定義することができます。

今回は、カスタムビューを作って、ジェスチャーイベントによって、背景色を変更するコードを作っていきたいと思います。

InteractionStyleViewというカスタムビューを作ります。
カスタムビューに`InteractionStyle`というenumを定義します。
タップしたとき、ロングタップしたとき、スワイプしたとき、何もしないときの状態と値を以下のように定義します。

```swift:InteractionStyleView.swift
class InteractionStyleView: UIView {

    enum InteractionStyle {
        case Tap(UIColor)
        case LongTap(UIColor)
        case Swipe(UIColor)
        case None(UIColor)
    }
}

```

次に各ジェスチャーに対応する`UIColor`インスタンスをプロパティで保持します。

```swift:InteractionStyleView.swift
    var tapColor    = UIColor.whiteColor()
    var longColor   = UIColor.whiteColor()
    var swipeColor  = UIColor.whiteColor()
    var noneColor   = UIColor.whiteColor()
```

続いて、プロパティとしてInteractionStyleを保持します。
この時に、`didSet`で代入されたときに各ジェスチャーの値を取得し、それぞれ対応する`UIColor`プロパティに代入します。

```swift:InteractionStyleView.swift
    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`プロパティにセットします。

```swift:InteractionStyleView.swift
   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
    }


```


コード全体では以下のとおりになります。

```swift:InteractionStyleView.swift

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`プロパティを代入すればよくなります。


```swift:ViewController.swift
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())
    }
}

```

これを実行するとタップは黒、長押しは赤、スワイプは黄色にビューが変わるようになります。

![RS02wFsD0j.gif](https://qiita-image-store.s3.amazonaws.com/0/31587/02881a25-2ab1-c389-8cc0-a816199824f5.gif "RS02wFsD0j.gif")


## このコードの利点

* 使う方(ViewController)はプロパティ代入すればよくなるからわかりやすくなる


## 欠点

* 定義する方（InteractionStyleView)では各状態のプロパティをそれぞれ保持するのでコード量が多くなる？

## 終わりに

プロパティ代入で状態と値の両方を設定する方法を見ていきました。
何かの訳に立てれば幸いです。

また、もっとやりようがありそうですが、今のところ思いつかなかったです。
もっといい方法があればコメントください。