Sponsor Link
環境&対象
- macOS Big Sur 11.1
- Xcode 12.3
- iOS 14.2
- SwiftyUserDefaults
Color を UserDefaults に保存
NSColor や UIColor もありますが、SwiftUI で使うのは、Color です。
UserDefaults は、アプリのちょっとした情報を保存することができ非常に便利ですが、保存できるタイプが制限されています。
Color は、保存できるタイプではありません。(UIColor, NSColor もサポート対象ではありません。)
この記事では、Color を UserDefaults に保存する方法を説明します。
SwiftyUserDefaults
UserDefaults を扱う時に便利な SwiftyUserDefaults を使います。
SwiftyUserDefaults は、こちら 。
DefaultsKEys の extension を定義することで、Defaultas[キー] のような形でのアクセスが可能となるライブラリです。
前準備
Color の情報は、RGB 情報で再構成できるはずなので、UserDefaults には、RGB 情報を保存しておくことにします。
しかし、問題がひとつあります。Color を RGB 値から作成することはできるのですが、Color から RGB 値を取得することはできません。
ですので、以下のような extension を作り、RGB(A) 値を取得できるようにしておきます。
#if os(iOS)
typealias SystemColor = UIColor
#elseif os(macOS)
typealias SystemColor = NSColor
#else
#error("your os is not supported")
#endif
extension SystemColor {
var rgba: (red: Double, green: Double, blue: Double, alpha: Double) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return (Double(red), Double(green), Double(blue), Double(alpha))
}
}
extension Color {
var rgbValues:(red: Double, green: Double, blue: Double){
let rgba = SystemColor(self).rgba
return (rgba.red, rgba.green, rgba.blue)
}
}
# iOS と macOS のどちらからも使えるように、UIColor/NSColor を typealias で別名定義して使用しています
Color を保存
2つの方法があります。少し方法が異なりますが、どちらも RGB 値を保存して、RGB 値から Color を再現するところは同じです。
直接 RGB 値を保存
1つの Color に対して、R/G/B それぞれの値を保存する用のキーを作成します。
extension DefaultsKeys {
// for Color1
var color1R:DefaultsKey { .init("Color1R", defaultValue: 0) }
var color1G:DefaultsKey { .init("Color1G", defaultValue: 0) }
var color1B:DefaultsKey { .init("Color1B", defaultValue: 0) }
}
// 読み込み箇所
init() {
color1 = Color(red: Defaults[\.color1R], green: Defaults[\.color1G], blue: Defaults[\.color1B])
}
// 保存箇所
didSet {
let rgb = color1.rgbValues
Defaults[\.color1R] = rgb.red
Defaults[\.color1G] = rgb.green
Defaults[\.color1B] = rgb.blue
}
Color を Serializable にして保存
Color を Codable に準拠させるような extension を用意して、Serializable な型とすることで、encode/decode の仕組みを使って保存します。
extension Color:Codable {
enum CodingKeys: String, CodingKey {
case red
case green
case blue
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let red = try values.decode(Double.self, forKey: .red)
let green = try values.decode(Double.self, forKey: .green)
let blue = try values.decode(Double.self, forKey: .blue)
self.init(red: red, green: green, blue: blue)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let rgba = self.rgbValues
try container.encode(rgba.red, forKey: .red)
try container.encode(rgba.green, forKey: .green)
try container.encode(rgba.blue, forKey: .blue)
}
}
extension Color:DefaultsSerializable {}
extension DefaultsKeys {
// for Color2
var color2:DefaultsKey { .init("Color2", defaultValue: Color.white)}
}
// 読み込み箇所
init() {
color2 = Defaults[\.color2]
}
// 保存箇所
didSet {
Defaults[\.color2] = self.color2
}
使い分け?
1つ目の方法は、自分で Color を分解して保存、読み込んで組み立てることをしています。
2つ目の方法は、Color を Codable に準拠させ、あとは、元からある仕組みを使って保存する方法です。
保存対象の Color が1つしかないのであれば、大きな差はありませんが、複数の Color を扱う必要があるのであれば、2つ目の方法が便利になりそうです。
テストに使ったコード
以下のコードを使ってテストしています。非常にシンプルなコードですが、参考までに貼っておきます。
//
// ContentView.swift
//
// Created by : Tomoaki Yagishita on 2021/01/16
// © 2021 SmallDeskSoftware
//
import SwiftUI
import SwiftyUserDefaults
#if os(iOS)
typealias SystemColor = UIColor
#elseif os(macOS)
typealias SystemColor = NSColor
#else
#error("os is not supported")
#endif
extension SystemColor {
var rgba: (red: Double, green: Double, blue: Double, alpha: Double) {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0
getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return (Double(red), Double(green), Double(blue), Double(alpha))
}
}
extension Color {
var rgbValues:(red: Double, green: Double, blue: Double){
let rgba = SystemColor(self).rgba
return (rgba.red, rgba.green, rgba.blue)
}
}
extension Color:Codable {
enum CodingKeys: String, CodingKey {
case red
case green
case blue
}
public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
let red = try values.decode(Double.self, forKey: .red)
let green = try values.decode(Double.self, forKey: .green)
let blue = try values.decode(Double.self, forKey: .blue)
self.init(red: red, green: green, blue: blue)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
let rgba = self.rgbValues
try container.encode(rgba.red, forKey: .red)
try container.encode(rgba.green, forKey: .green)
try container.encode(rgba.blue, forKey: .blue)
}
}
extension Color:DefaultsSerializable {}
class ColorModel {
var color1: Color {
didSet {
let rgb = color1.rgbValues
Defaults[\.color1R] = rgb.red
Defaults[\.color1G] = rgb.green
Defaults[\.color1B] = rgb.blue
}
}
var color2: Color = Color.white {
didSet {
Defaults[\.color2] = self.color2
}
}
init() {
color1 = Color(red: Defaults[\.color1R], green: Defaults[\.color1G], blue: Defaults[\.color1B])
color2 = Defaults[\.color2]
}
}
class ViewModel: ObservableObject {
@Published var colors: ColorModel
public init() {
colors = ColorModel()
}
}
struct ContentView: View {
@StateObject private var colorModel: ViewModel = ViewModel()
var body: some View {
VStack {
GroupBox(label: Text("SavedColor")) {
Text("Color1 is \(colorModel.colors.color1.description)")
.padding()
ColorPicker("Color1", selection: $colorModel.colors.color1)
}
GroupBox {
Text("Color2 is \(colorModel.colors.color2.description)")
.padding()
ColorPicker("Color2", selection: $colorModel.colors.color2)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension DefaultsKeys {
// for Color1
var color1R:DefaultsKey { .init("Color1R", defaultValue: 0) }
var color1G:DefaultsKey { .init("Color1G", defaultValue: 0) }
var color1B:DefaultsKey { .init("Color1B", defaultValue: 0) }
// for Color2
var color2:DefaultsKey { .init("Color2", defaultValue: Color.white)}
}
まとめ:Color を UserDefaults に保存する
- RGB 値それぞれを個別に保存する
- Color を Codable 準拠にして保存する
- (補足) Color から RGB 値を取得するときは、UIColor/NSColor を使うと便利
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Sponsor Link