[SwiftUI]UserDefaultsを使用せず@AppStorageでデータを永続的に保存する方法と注意点
はじめに
この記事では@AppStorageを使用してUserDefaultsのようにデータを永続的に保存する方法とその注意点についてわかった事、試してみた事を紹介したいと思います。
@AppStorage
@AppStorageは値を自動的にUserDefaultsに保存してくれるproperty wrapperです。
公式のドキュメント
UserDefaultsより非常にシンプルに書けるますが、iOS14〜とまだ新しく、少し注意しないといけない点があると感じました。
環境
・ macOS: Monterey
・ Xcode: 13.2
・ iOS: iOS 15.2
基本的な使い方
使用方法は以下の通りです。コードが1行のみと非常にシンプルです。
@AppStorage("testKey") var testAppStorage = ""
@AppStorageの後に書いてある("testKey")がKeyになるのでここが同じならプロパティ名は違っても同じKey名内にデータが上書き保存されていきます。
また以下はTextFieldに文字を書いて、Textに表示させるシンプルなViewです。
下記ののコードを実行するとTextFieldに文字を書いて、Textに表示させるシンプルなViewが表示されます。また書いた文字が永続的に保存されていることを確認できます。
@AppStorage("testKey") var testAppStorage = ""
var body: some View {
VStack {
TextField("input text", text: $testAppStorage)
Text(testAppStorage)
Button {
testAppStorage.removeAll()
} label: {
Text("削除")
}
}
}
削除には.removeAll()を使用しています。
ObservableObjectに準拠させたclassでデータを保持する
View外のclassで@AppStorageを使用してもclassをインスタンス化しても使用することができません。
//class
class TestAppStrageClass {
@AppStorage("testKey") var testAppStorage = ""
}
//view
struct TestAppStoregeView2: View {
var testModel = TestAppStrageClass()
var body: some View {
VStack {
TextField("input text", text: $testModel.testAppStorage)
Text(testModel.testAppStorage)
Button {
testModel.testAppStorage.removeAll()
} label: {
Text("削除")
}
}
}
}
しかしclassをObservableObjectに準拠させとインスタンス化する際に@ObservedObjectを付けるとclass側でデータの保存を行うことができます。
class TestAppStrageClass:ObservableObject {
@AppStorage("testKey") var testAppStorage = ""
}
//View
struct TestAppStoregeView2: View {
@ObservedObject var testClass = TestAppStrageClass()
var body: some View {
VStack {
TextField("input text", text: $testClass.testAppStorage)
Text(testClass.testAppStorage)
Button {
testClass.testAppStorage.removeAll()
} label: {
Text("削除")
}
}
}
}
注意点
ここからは注意点になります。
先ほどclassで使用できると書きましたが、実際に使用する場合、一つのclassでデータの管理を行い、複数のViewで使用する状況があると思います。
以下TabViewを使用して試してみました。
//View1
struct TestAppStoregeView: View {
@AppStorage("testKey") var testAppStorage = ""
var body: some View {
VStack {
TextField("input text", text: $testAppStorage)
Text(testAppStorage)
Button {
testAppStorage.removeAll()
} label: {
Text("削除")
}
}
}
}
//View2
struct TestAppStoregeView2: View {
@ObservedObject var testClass = TestAppStrageClass()
var body: some View {
VStack {
TextField("input text", text: $testClass.testAppStorage)
Text(testClass.testAppStorage)
Button {
testClass.testAppStorage.removeAll()
} label: {
Text("削除")
}
}
}
}
//View3
struct TestAppStoregeView3: View {
@ObservedObject var testClass = TestAppStrageClass()
var body: some View {
VStack {
TextField("input text", text: $testClass.testAppStorage)
Text(testClass.testAppStorage)
Button {
testClass.testAppStorage.removeAll()
} label: {
Text("削除")
}
}
}
}
//class
class TestAppStrageClass:ObservableObject {
@AppStorage("testKey") var testAppStorage = ""
}
結果
いかがでしょうか?
上記のようにView1では直接property wrapperを書きましたが、その他はclassからObservableObjectに準拠したプロパティを通してデータを更新しました。その結果データが一度キャッシュされ、リアルタイムで表示することができませんでした。
まとめ
非常に便利に使用できますが、MVVMなどで使用する際は注意が必要かなと思いました。
現状はViewで直接使用することが良いかもしれません。
どうしてもMVVMで保持させたい場合はUserDefaults使用する事が現状無難かと思います。
Discussion