enum切り替え用のValueSwitcherってのを作ってみたが(わかりづらいので微妙)

March 06, 2016

Swift の Value を切り替えて保存する ValueSwitcher ってのを作った。Qiita に投稿しない。Carthage 対応しない。Cocoapods 公開しない。不完全だと思うので非公開ライブラリとして。

これ、自分でも必要性がすごくピンときているわけではない。せっかく作ったし、あとで読み返して考えるためにもブログに書いてみる。そういう感じです。

なぜ作ったのか

foregroundColor, backgroundColor, blur など複数のカラーをまとめた ColorSet という enum があって、それをアプリ内で切り替えていた。アプリを開き直したらそのセットが適用されるようになっていた。これを複数ターゲットで使おうと思ったが、色のセットは微妙に異なることがわかった。

それで共通部分を切り出して自分用ライブラリにしようと思った。

最初は、色の切り替え専用でいいやと思ってたんだけど、考えてみたら別に色のセットだけじゃなくていろんなものに適用できそうだったのでもう少し汎用的にした。

ただそれだけです。

コード

コードをそのまま貼り付けます。

import Foundation

public struct ValueSwitcherNotificationName {
    public static let Changed = "\[REVERSE\_DOMAIN\].value\_switcher.changed"
}

public func ==<T: SwitchableValueType> (rhs: T, lhs: T) -> Bool {
    return rhs.id == lhs.id
}

public protocol SwitchableValueType: Hashable, Equatable {
    static var allValues: \[Self\] { get }
    static var defaultValue: Self { get }
    static var identifier: String { get }

    var id: String { get }
}

public extension SwitchableValueType {
    public var hashValue: Int {
        return id.hashValue
    }
}

public struct ValueSwitcher<SwitchableValue: SwitchableValueType> {
    public static func allValues() -> Set<SwitchableValue> {
        return Set<SwitchableValue>(SwitchableValue.allValues)
    }

    public static func defaultValue() -> SwitchableValue {
        return SwitchableValue.defaultValue
    }

    public static func findValue(id: String?) -> SwitchableValue {
        guard let id = id else {
            return defaultValue()
        }
        guard let value = allValues().filter({ $0.id == id }).first else {
            return defaultValue()
        }
        return value
    }

    public static func currentValue() -> SwitchableValue {
        let storedId = NSUserDefaults.standardUserDefaults().objectForKey(SwitchableValue.identifier) as? String
        let id = storedId ?? SwitchableValue.defaultValue.id
        return findValue(id)
    }

    public static func updateValue(value: SwitchableValue) {
        NSUserDefaults.standardUserDefaults().setObject(value.id, forKey: SwitchableValue.identifier)
        NSUserDefaults.standardUserDefaults().synchronize()
        NSNotificationCenter.defaultCenter().postNotificationName(ValueSwitcherNotificationName.Changed, object: nil, userInfo: \["identifier": SwitchableValue.identifier\])
    }
}

利用時

DemoColor という enum を作った。これは、SwitchableValueType に準拠している。

import Foundation
import SwiftValueSwitcher
import UIKit

struct Color {
    static let Blue: UIColor        = .blueColor()
    static let Red: UIColor         = .redColor()
    static let White: UIColor       = .whiteColor()
    static let Black: UIColor       = .blackColor()
}

enum DemoColor: SwitchableValueType {
    case Blue
    case Red
    case Black
    case White

    static var allValues: \[DemoColor\] {
        return \[.Blue, .Red, .Black, .White\]
    }

    static var defaultValue: DemoColor {
        return .Blue
    }

    static var identifier: String {
        return "\[REVERSE\_DOMAIN\].StopwatchColor"
    }

    var id: String {
        switch self {
        case .Blue:         return "blue"
        case .Red:          return "red"
        case .Black:        return "black"
        case .White:        return "white"
        }
    }

    var foregroundColor: UIColor {
        switch self {
        case .Blue:         return Color.Blue
        case .Red:          return Color.Red
        case .Black:        return Color.Black
        case .White:        return Color.White
        }
    }

    var backgroundColor: UIColor {
        switch self {
        case .Blue:         return Color.Black
        case .Red:          return Color.Black
        case .Black:        return Color.White
        case .White:        return Color.Black
        }
    }

    var name: String {
        switch self {
        case .Blue:         return "BLUE"
        case .Red:          return "RED"
        case .Black:        return "BLACK"
        case .White:        return "WHITE"
        }
    }
}

これを下記のように使う。

import UIKit
import SwiftValueSwitcher

class ViewController: UIViewController {

    @IBOutlet weak var colorNameLabel: UILabel!
    @IBOutlet weak var updateButton: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()

        NSNotificationCenter.defaultCenter().addObserver(self, selector: "colorChanged:", name: ValueSwitcherNotificationName.Changed, object: nil)
        update()
    }

    // MARK: Notifications

    @objc
    func colorChanged(sender: NSNotification) {
        print(sender.userInfo)
    }

    // MARK: Actions

    @IBAction func updateButtonTapped(sender: AnyObject) {
        let colors = ValueSwitcher<DemoColor>.allValues()
        let index = Int(arc4random\_uniform(UInt32(colors.count)))
        let color = Array(colors)\[index\]
        ValueSwitcher<DemoColor>.updateValue(color)
        update()
    }

    // MARK: Update

    func update() {
        let color = ValueSwitcher<DemoColor>.currentValue()
        view.backgroundColor = color.backgroundColor
        colorNameLabel.text = color.name
        colorNameLabel.textColor = color.foregroundColor
        updateButton.setTitleColor(color.foregroundColor, forState: .Normal)
    }
}

微妙なところ

ValueSwitcherはジェネリクスなので何か型を当てはめて使うんだけど、この構造がちょっと微妙。いろんな型に対応したいけど、id で扱う対象を分けるとかもう少し分離できそう。とりあえず、今日はここでやめるけど、もっと汎用的になったら使いやすくなるかも。

Value は enum を前提としているので分かりづらい。

そもそも、value のセットを扱うだけのライブラリは大げさではないか。


Profile picture

Written by morizotter who lives and works in Tokyo building useful things. You should follow them on Twitter