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 のセットを扱うだけのライブラリは大げさではないか。