質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

4219閲覧

【SwiftUI, Swift】UserDefaultsで独自に定義した型の配列が保存できません。

taichi_dm_

総合スコア60

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

1クリップ

投稿2020/04/12 14:55

##【SwiftUI, Swift】UserDefaultsで独自に定義した型の配列が保存できません。

###SceneDelegate.swift

SwiftUI

1import UIKit 2import SwiftUI 3class SceneDelegate: UIResponder, UIWindowSceneDelegate { 4 5 var window: UIWindow? 6 7 8 func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 9 let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext 10 11 12 13 let contentView = ContentView() 14 .environment(.managedObjectContext, context) 15 .environmentObject(GlobalData()) 16 17 18 if let windowScene = scene as? UIWindowScene { 19 let window = UIWindow(windowScene: windowScene) 20 window.rootViewController = UIHostingController(rootView: contentView) 21 self.window = window 22 window.makeKeyAndVisible() 23 } 24 } 25 26 func sceneDidDisconnect(_ scene: UIScene) { 27 } 28 29 func sceneDidBecomeActive(_ scene: UIScene) { 30 } 31 32 func sceneWillResignActive(_ scene: UIScene) { 33 } 34 35 func sceneWillEnterForeground(_ scene: UIScene) { 36 } 37 38 func sceneDidEnterBackground(_ scene: UIScene) { 39 (UIApplication.shared.delegate as? AppDelegate)?.saveContext() 40 } 41 42 43}

###ContentView.swift

SwiftUI

1struct ContentView: View { 2 @EnvironmentObject var globalData: GlobalData 3 var body: some View { 4 Button(action: { 5 self.globalData.data.append(hogeStruct(text: "テキスト", weather: .sunny)) 6 self.globalData.saveData() 7 }) { 8 Text("Add Data") 9 } 10 } 11} 12 13class GlobalData: ObservableObject { 14 @Published var data: [hogeStruct] = [] 15 16 func saveData() { 17 let u = UserDefaults.standard 18 u.set(self.data, forKey: "data")//ここでエラーが出る。 19 } 20} 21 22struct hogeStruct { 23 var id = UUID() 24 var text: String 25 var weather: Weather 26 27 enum Weather: CaseIterable { 28 case sunny 29 case cloudy 30 } 31} 32

↑問題の再現に必要なコードです。

###エラーが出る箇所

SwiftUI

1let u = UserDefaults.standard 2u.set(self.data, forKey: "data")//ここ!

独自にhogeStruct型を宣言し、その配列をUserDefaultsに保存しようとしてエラーが出ます。
hogeStruct型の配列をスマホ内に保存する方法は他にありますか?

###試したこと
Core Dataを使う
しかし独自の型は保存できない仕様のようです。(HashValueが関係している???)

hogeStruct型をHashableに準拠させるのでしょうか。

ご教授よろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

一番簡単なのは保存したいstruct/enumをCodableにしてしまう方法です

swift

1struct hogeStruct: Codable { 2 var id = UUID() 3 var text: String 4 var weather: Weather 5 6 // 用途がよくわからないのでとりあえずStringで 7 enum Weather: String, CaseIterable, Codable { 8 case sunny 9 case cloudy 10 } 11}

これでさらにUserDefaultsを以下のように拡張すれば簡単に利用できるようになります。

swift

1 2extension UserDefaults { 3 func setEncoded<T: Encodable>(_ value: T, forKey key: String) { 4 guard let data = try? JSONEncoder().encode(value) else { 5 print("Can not Encode to JSON.") 6 return 7 } 8 9 set(data, forKey: key) 10 } 11 12 func decodedObject<T: Decodable>(_ type: T.Type, forKey key: String) -> T? { 13 guard let data = data(forKey: key) else { 14 return nil 15 } 16 17 return try? JSONDecoder().decode(type, from: data) 18 } 19}

追記

このUserDefaultsの使い方は

保存

swift

1 2let values: [hogeStruct] = ... //ここに保存対象が入っているとします 3 4UserDefaults.standard.setEncoded(values, forKey: "KEY")

取り出し

swift

1// Optional<[hogeStruct]>型になります。 2let optionalValues = UserDefaults.standard.decodedObject([hogeStruct].self, forKey: "KEY") 3 4// nilなら空の配列とする時 5let values = UserDefaults.standard.decodedObject([hogeStruct].self, forKey: "KEY") ?? [] 6

最初の引数に取り出す値の型に.selfを付けたものを与えます。
これによってキャストなしに目的の型(のOptional型)で取得できます。

投稿2020/04/13 06:26

編集2020/04/15 07:48
MasakiHori

総合スコア3384

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

taichi_dm_

2020/04/14 07:44

回答ありがとうございます! 使う時はUserdefaults.standard.set(data, forKey: "data")ですか? このまま使ったらエラーになりました。
MasakiHori

2020/04/14 09:08

エラーの内容が分からないと何もわかりません 上のCodableにしたhogeStructを利用していますか?
taichi_dm_

2020/04/15 05:12

申し訳ありません。hogeStructを使用しています。 UserDefaults.standard.setを使用した際、 Thread 1: Exception: "Attempt to insert non-property list object (\n \"Experience_Project.hogeStruct(id: F196BC40-530C-46E0-9764-7A1A98293DEA, text: \\"\U30c6\U30b9\U30c8\\", weather: Experience_Project.hogeStruct.Weather.sunny)\"\n) for key array" こんなエラーが出ました。 そこで、先ほどextension内で定義されていたUserDefaults.standard.setEncodedを使ったらUserDefaultsに書き込むことはおそらく成功しました。 しかし、読み取りはどうすれば良いでしょうか。 self.globalData = UserDefaults.standard.array(forKey: "array") as! [hogeStruct] これを実行したら、Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional valueというエラーが出ました。
MasakiHori

2020/04/15 07:39

すみません。使い方難しいですね。説明追加します。
taichi_dm_

2020/04/15 14:50

ありがとうございます 無事、期待通りの動作が確認できました 丁寧に使い方まで説明していただきありがとうございます!
guest

0

UserDefaultではカスタムクラス(というか、プロパティリスト以外のインスタンス)は保存できません。

https://developer.apple.com/documentation/foundation/userdefaults

A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you should typically archive it to create an instance of NSData.

カスタムクラスの保存例は別の回答の後半にも書いていますので、こちらの方法でためしてもらえますでしょうか。

投稿2020/04/12 15:04

TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問