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

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

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

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

Q&A

解決済

2回答

418閲覧

カスタムセルをUDを使用して保存したい

Ytan

総合スコア39

Swift

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

0グッド

0クリップ

投稿2020/07/03 14:19

実現したいこと

TodoListのcellをUserDefaultsを使用して保存したい。

Swift5

1import UIKit 2 3class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource { 4 5 var todos:[Item] = [] 6 7 @IBOutlet weak var Table:UITableView! 8 9 override func viewDidLoad() { 10 super.viewDidLoad() 11 // Do any additional setup after loading the view 12 Table.delegate = self 13 Table.dataSource = self 14 15 if UserDefaults.standard.object(forKey: "todoList") != nil{ 16 todos = UserDefaults.standard.object(forKey: "todoList") as! [Item] 17 } 18 } 19 20 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 21 return todos.count 22 } 23 24 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 25 26 let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) 27 let item = todos[indexPath.row] 28 cell.textLabel!.text = item.title 29 return cell 30 } 31 32 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 33 return view.frame.size.height/10 34 } 35 36 @IBAction func addNewTodo(_sender: Any){ 37 var textField = UITextField() 38 39 let alert = UIAlertController(title: "新しいTodoを追加", message: "", preferredStyle: .alert) 40 41 let action = UIAlertAction(title: "リストに追加", style: .default) { (action) in 42 43 let newItem:Item = Item(title: textField.text!) 44 print("追加されました") 45 46 //UD保存 47 UserDefaults.standard.set(self.todos, forKey: "todoList") 48 49 self.todos.append(newItem) 50 self.Table.reloadData() 51 } 52 53 alert.addTextField { (alertTextField) in 54 55 alertTextField.placeholder = "新しいTodo" 56 textField = alertTextField 57 } 58 59 alert.addAction(action) 60 present(alert, animated: true,completion: nil) 61 62 } 63 64 //swipe action 65 func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { 66 67 // シェアのアクションを設定する 68 let shareAction = UIContextualAction(style: .normal , title: "share") { 69 (ctxAction, view, completionHandler) in 70 print("シェアを実行する") 71 completionHandler(true) 72 } 73 // シェアボタンのデザインを設定する 74 let shareImage = UIImage(systemName: "square.and.arrow.up")?.withTintColor(UIColor.white, renderingMode: .alwaysTemplate) 75 shareAction.image = shareImage 76 shareAction.backgroundColor = UIColor(red: 0/255, green: 125/255, blue: 255/255, alpha: 1) 77 78 // 削除のアクションを設定する 79 let deleteAction = UIContextualAction(style: .destructive, title:"delete") { 80 (ctxAction, view, completionHandler) in 81 self.todos.remove(at: indexPath.row) 82 tableView.deleteRows(at: [indexPath], with: .automatic) 83 completionHandler(true) 84 tableView.reloadData() 85 } 86 87 // スワイプでの削除を無効化して設定する 88 let swipeAction = UISwipeActionsConfiguration(actions:[deleteAction, shareAction]) 89 swipeAction.performsFirstActionWithFullSwipe = false 90 91 return swipeAction 92 } 93} 94 95struct Item{ 96 97 var title:String 98 99 init(title:String) { 100 self.title = title 101 } 102}

エラー&疑問点

cellを追加するところまでは良くて保存のコード(UD)を記入したところ
一つ目のcellは追加されるのですが、二個目以降から以下のエラー文が出ます。

Thread 1: Exception: "Attempt to insert non-property list object (\n \"SwiftTodoListTest.Item(title: \\"a\\")\"\n) for key todoList"

似たような状況になっている方の質問です
自分もItem型にtodosをcastしているので対処法や分かりやすいサイトなどを教えていただけませんか?
またUDを使用せずに保存する方法はありますか?

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

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

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

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

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

guest

回答2

0

NSCodingは(事実上)NSObjectのサブクラスであることを要求し、またNSKeyedUnarchiverはSwiftとは相性が悪い(型情報が消失する)のでCodableを使いましょう。

ということでQiitaにエントリ書きました。

コピペしてプロジェクトに追加するだけです。

使い方はいたって簡単です。
まず保存したいデータ型をCodableにします。

swift

1 2struct Item: Codable { 3 4 var title:String 5 6 init(title:String) { 7 self.title = title 8 } 9}

この場合は後に: Codableを付けるだけです。あとはコンパイラが自動でやってくれます。

保存

swift

1 2UserDefaults.standard.setEncoded(self.todos, forKey: "todoList")

取り出し

swift

1todos = UserDefaults.standard.decodedObject([Item].self, forKey: "todoList") ?? []

型が明確に与えられているのでキャストの必要はありません。

投稿2020/07/04 08:43

MasakiHori

総合スコア3384

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

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

Ytan

2020/07/04 12:31

Codableのみで良いのですね! Qiitaの記事もありがとうございます。
guest

0

ベストアンサー

リンク先の回答にありますが

その通りです、UserDefaultsにはカスタムクラスはそのままでは保存できません、保存するのでしたらNSCodingプロトコルに準拠させないといけません。

カスタムクラスや構造体はそのままでは保存することはできません。

Appleの公式ドキュメントをみると以下のような記述があります。

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.

つまり、プロパティリストとして保存できるデータ型(NSData, NSString, NSNumber, NSData, NSArray, NSDictionary) やその組み合わせのインスタンスでなければならないわけです。

したがって、先の回答にもありましたように、Itemを仕様に沿って変更する必要があります。

Swift

1// MARK: Item を NSOBject を継承し、NSCoding に準拠した class として宣言する 2//struct Item { 3class Item: NSObject, NSCoding { 4 var title:String 5 6 init(title:String) { 7 self.title = title 8 } 9 10 func encode(with aCoder: NSCoder) { 11 aCoder.encode(self.title, forKey: "title") 12 } 13 14 required init?(coder aDecoder: NSCoder) { 15 self.title = aDecoder.decodeObject(forKey: "title") as! String 16 } 17}

保存や読み込みは次のような感じになります。

Swift

1 // MARK: 読み込み時の処理 2// if UserDefaults.standard.object(forKey: "todoList") != nil{ 3// todos = UserDefaults.standard.object(forKey: "todoList") as! [Item] 4// } 5// 6 if let data = UserDefaults.standard.object(forKey: "todoList") as? Data { 7 todos = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [Item] 8 }

Swift

1 // MARK: シリアライズしてから保存する 2 //UD保存 3 let data = try! NSKeyedArchiver.archivedData(withRootObject: self.todos, requiringSecureCoding: false) 4 UserDefaults.standard.set(data, forKey: "todoList")

今後、ToDoの項目が増えていくかと思いますが、同じような感じで処理をくりかえすことになります。

あるいは、現在のように ToDo の項目が一つだけしかないのであれば、Class や Struct にせず、単に[String]としてtodosを宣言すれば、カスタムクラスを作らずに標準のメソッドで保存することも可能です。

投稿2020/07/03 21:37

TsukubaDepot

総合スコア5086

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

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

Ytan

2020/07/04 12:18

他の回答者様はCodableを使用しているのですが、実際はどちらのが使いやすいのですかね?
TsukubaDepot

2020/07/04 22:28

使いやすいのは Codable を使う方だと思いますが、実際にやってみるのが良いと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問