はじめに
この記事は、[SwiftUI] 2.カウンターアプリの機能を増やそう!の続きになります。まだ読んでいないという人はそちらを先に読むことをおすすめしましす。
それでは今回もがんばりましょう!
環境
- Mac mini 2020
- チップ: Apple M1
- メモリ: 16GB
- OS: macOS Sequoia
- ツール: Xcode v16.3(バージョンアップ)
つくるもの
Tapボタンで数字を上げる
タスクキルをする
もう一度立ち上げると状態が保存されている
そして、アンインストールすると状態が初期化される
このように、タスクキルしても状態が保存されるようなアプリをつくっていきましょう!
TODO
- 色を変えるロジックを切り出す
-
@AppStorage
を指定してデータを保存する
開発
それでは、TODOに沿って開発をしていきましょう!
色を変えるロジックを切り出す
それでは現状のソースコードを見てみましょう。
import SwiftUI
struct ContentView: View {
@State var count: Int = 0
@State var textColor: Color = .black
var body: some View {
VStack {
Text(String(count))
.font(.largeTitle)
.fontWeight(.regular)
.foregroundColor(textColor)
HStack {
Button("Reset") {
count = 0
textColor = .black
}
Button("Tap") {
count += 1
if (count % 3 == 0) {
textColor = .red
} else {
textColor = .black
}
}
}
}
.padding()
}
}
#Preview {
ContentView()
}
これを見て、ちょっと修正したいなと感じるところがあると思います。
そう、textColor
を変更するところがちょっとごちゃっとしてるんですよね。なぜなら、count
を計算するところとtextColor
を計算するところが同じで、かつButton("Reset")
にもtextColor
のロジックが記述されているため、全体的に一貫性がないように感じるからです。
直し方としては、countに応じて適切な色を返す計算プロパティをつくってそれを使うことにしましょう。
var colorForMultipleOf3: Color {
if (count % 3 == 0) {
return .red
} else {
return .black
}
}
単純に考えればこのような実装になると思うのですが、このままではcountが0のときにもテキストが赤になってしまって元の挙動と変わってしまうので、 countが0ではない という条件も付け足しましょう。
var colorForMultipleOf3: Color {
if (count % 3 == 0 && count != 0) {
return .red
} else {
return .black
}
}
これで動きはするんですが、この程度の処理なら三項演算子を使えばもっと簡単に記述できそうですね。
var colorForMultipleOf3: Color {
return (count % 3 == 0 && count != 0) ? .red : .black
}
そして、最後にSwiftではreturn
を省略できるという文法があるため
var colorForMultipleOf3: Color {
(count % 3 == 0 && count != 0) ? .red : .black
}
こんな感じで書くとスッキリしますね。
では、これをプログラムに組み込みましょうか。
この計算プロパティは、.foregroundColor
に直接指定してやればよさそうですね。
Text(String(count))
.font(.largeTitle)
.fontWeight(.regular)
.foregroundColor(colorForMultipleOf3)
こんな感じで大丈夫です。
それでは無駄な部分を消して実装しましょう。
import SwiftUI
struct ContentView: View {
@State var count: Int = 0
@State var textColor: Color = .black
var body: some View {
VStack {
Text(String(count))
.font(.largeTitle)
.fontWeight(.regular)
.foregroundColor(colorForMultipleOf3)
HStack {
Button("Reset") {
count = 0
}
Button("Tap") {
count += 1
}
}
}
.padding()
}
var colorForMultipleOf3: Color {
(count % 3 == 0 && count != 0) ? .red : .black
}
}
#Preview {
ContentView()
}
これでソースコードがスッキリしましたね。
@AppStorage
を指定してデータを保存する
それではここからが本番です!
今の状態では、count
を上げてもタスクキルしてしまうとcount
が0になってしまいます。
ですが、今回はタスクキルしてもcount
を保存できるようにします。
それを実現するためにUserDefaults
という仕組みを使います。
UserDefaultsはAppleの公式ドキュメントで
An interface to the user’s defaults database, where you store key-value pairs persistently across launches of your app.
と説明されています。
これを翻訳すると
ユーザーのデフォルト・データベースへのインターフェイスで、アプリの起動に渡ってキーと値のペアを永続的に保存します。
となります。
簡単に説明すると、データベースにキーと値のペアを保存できるもの です。
そして、この仕組みはアプリを削除すると保存したデータも削除されるため今回に適しているといえるでしょう。
これは永続的にデータを保存できるものの、アプリを削除してしまうとデータが消えるため重要なデータを保存しないようにしましょう。
そして、SwiftUIには@AppStorage
という便利な仕組みがあり、これを使えばUserDefaultsを簡単に扱えます。
使い方は
@AppStorage("キー") var 変数名: 型
となります。
ここでの"キー"
は値を識別するためのキーになります。できるだけ何の値なのかわかりやすい名前を付けておきましょう。
ここで注意なのですが、@AppStorage
を使えるのは
- Bool
- String
- Int
- Double
- Float
- Data
- URL
基本的にこれらなので、これ以外を保存したいときはUserDefaults(まだ保存できる型が多いため)を使うか、どうにか工夫して使ってください。
では実装していきましょうか。
今回は、count
を保存していけばいいので
@AppStorage("CountKey") var count: Int = 0
こんな感じでいいと思います。
キーは適当につけちゃったんですが、無難にcount
とかでもよかった気がします笑
では実際のコードに実装しましょうか。
import SwiftUI
struct ContentView: View {
@AppStorage("CountKey") var count: Int = 0
@State var textColor: Color = .black
var body: some View {
VStack {
Text(String(count))
.font(.largeTitle)
.fontWeight(.regular)
.foregroundColor(colorForMultipleOf3)
HStack {
Button("Reset") {
count = 0
}
Button("Tap") {
count += 1
}
}
}
.padding()
}
var colorForMultipleOf3: Color {
(count % 3 == 0 && count != 0) ? .red : .black
}
}
#Preview {
ContentView()
}
こんな感じですね。
ここで、鋭い人は気がついたでしょう。
「@State
を消してもUIに反映されるのか」
そうです。本来は@State
をつけなければUIには反映されないはずです。
しかし、SwiftUIには@AppStorage
の値が変更されるとViewが再描画されるという仕組みがあります。
つまり、@State
も一緒にしてくれるっていうことですね。
(いやーべんりべんり)
というわけで、これで完成です
UserDefaultsについて
ちょっとUserDefaultsの使い方も書いておこうと思います。
UserDefaultsを使ったデータの保存と取り出し方を紹介します
- 保存
UserDefaults.standard.set(値, forKey: "キー")
- 取り出す
UserDefaults.standard.object(forKey: "キー")
UserDefaults.standard.string(forKey: "キー")
UserDefaults.standard.array(forKey: "キー")
UserDefaults.standard.dictionary(forKey: "キー")
UserDefaults.standard.data(forKey: "キー")
UserDefaults.standard.stringArray(forKey: "キー")
UserDefaults.standard.integer(forKey: "キー")
UserDefaults.standard.float(forKey: "キー")
UserDefaults.standard.double(forKey: "キー")
UserDefaults.standard.bool(forKey: "キー")
UserDefaults.standard.url(forKey: "キー")
取り出すには型にあった関数を使う
- 削除
UserDefaults.standard.removeObject(forKey: キー)
- 全削除
let appDomain = Bundle.main.bundleIdentifier
UserDefaults.standard.removePersistentDomain(forName: appDomain!)
ここで注意なのですが、値を取り出すときにオプショナル型で返ってくるものと非オプショナル型で返ってくるものがあります。
メソッド名 | 返り値の型 | オプショナル? | 説明 |
---|---|---|---|
string(forKey:) |
String? |
✅ Yes | 文字列が存在しないと nil
|
array(forKey:) |
[Any]? |
✅ Yes | 配列が存在しないと nil
|
dictionary(forKey:) |
[String: Any]? |
✅ Yes | 辞書が存在しないと nil
|
data(forKey:) |
Data? |
✅ Yes | データが存在しないと nil
|
object(forKey:) |
Any? |
✅ Yes | 値が存在しないと nil
|
url(forKey:) |
URL? |
✅ Yes | URLが存在しないと nil
|
stringArray(forKey:) |
[String]? |
✅ Yes | 文字列配列が存在しないと nil
|
bool(forKey:) |
Bool |
❌ No | 値が存在しない場合は false を返す |
integer(forKey:) |
Int |
❌ No | 値が存在しない場合は 0 を返す |
float(forKey:) |
Float |
❌ No | 値が存在しない場合は 0.0 を返す |
double(forKey:) |
Double |
❌ No | 値が存在しない場合は 0.0 を返す |
こんな感じで、型によって違うので気をつけてください。
そして、今回のソースコードをUserDefualts
を使ったやり方で書いてみようと思います。
import SwiftUI
struct ContentView: View {
@State private var count: Int = UserDefaults.standard.integer(forKey: "CountKey")
@State var textColor: Color = .black
var body: some View {
VStack {
Text(String(count))
.font(.largeTitle)
.fontWeight(.regular)
.foregroundColor(colorForMultipleOf3)
HStack {
Button("Reset") {
count = 0
UserDefaults.standard.set(0, forKey: "CountKey")
}
Button("Tap") {
count += 1
UserDefaults.standard.set(count, forKey: "CountKey")
}
}
}
.padding()
}
var colorForMultipleOf3: Color {
(count % 3 == 0 && count != 0) ? .red : .black
}
}
#Preview {
ContentView()
}
少し適当になってしまった気もするのですが、こんな感じで実装できます。
実行したらわかるのですが同じ挙動をすると思います。
おわりに
今回は、@AppStorage
とUserDefaults
を使ってタスクキルをしても情報を保存するプログラムを書きました。
UserDefaults
も簡単に扱えますが、@AppStorage
はもっと簡単に扱えてとても便利ですね!
では次回もがんばっていきましょうー!お疲れ様でしたー!
次回: [SwiftUI入門] 4.NavigationStackでページの遷移をしよう!
前回: [SwiftUI] 2.カウンターアプリの機能を増やそう!