🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Xcode

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

Swift

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

Q&A

解決済

1回答

4932閲覧

SwiftでAttempt to insert non-property list objectが出た時の対処について

s1209

総合スコア13

Xcode

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

Swift

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

0グッド

1クリップ

投稿2018/05/06 13:10

編集2018/05/07 14:07

前提・実現したいこと

Swiftでアプリを作成していますが、途中で(完了ボタンを押した時)エラーが出てしまいます。

発生している問題・エラーメッセージ

2018-05-06 21:47:14.344143+0900 timeTableApp[5738:240658] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object ( "timeTableApp.Class" ) for key TimeTable'

該当のソースコード (一部省略)

Swift

1 2import UIKit 3 4class Class { 5 let className: String 6 let roomName: String 7 let cellNumber: Int 8 9 init(className: String, roomName: String, cellNumber: Int) { 10 11 self.className = className 12 self.roomName = roomName 13 self.cellNumber = cellNumber 14 } 15} 16 17class Edit: UIViewController, UITextFieldDelegate { 18 19 @IBOutlet weak var classTextField: UITextField! 20 @IBOutlet weak var roomTextField: UITextField! 21 22 override func viewDidLoad() { 23 super.viewDidLoad() 24 25 classTextField.delegate = self 26 roomTextField.delegate = self 27 28 let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGesture)) 29 self.view.addGestureRecognizer(tapRecognizer) 30 } 31 32 @objc func tapGesture() { 33 classTextField.resignFirstResponder() 34 roomTextField.resignFirstResponder() 35 } 36 37 func textFieldShouldReturn(_ textField: UITextField) -> Bool { 38 classTextField.resignFirstResponder() 39 roomTextField.resignFirstResponder() 40 41 return true 42 } 43 44 45// 完了ボタンを押した時 46 @IBAction func tappedButton(_ sender: UIButton) { 47 48 let classData = Class(className:classTextField.text!, roomName: roomTextField.text!, cellNumber: ViewController.cellNumber) 49 50 ViewController.testArray.append(classData) 51 UserDefaults.standard.set(ViewController.testArray, forKey: "TimeTable") 52 UserDefaults.standard.synchronize() 53 54 print(ViewController.testArray.count) 55 56 dismiss(animated: true) {} 57 } 58 59} 60

Swift

1 2import UIKit 3 4class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { 5 6 static var cellNumber: Int = 0 7 @IBOutlet weak var collectionView: UICollectionView! 8 9 static var testArray: [Class] = [] 10 11 override func viewDidLoad() { 12 super.viewDidLoad() 13 14 collectionView.delegate = self 15 collectionView.dataSource = self 16 17 collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell") 18 } 19 20 override func viewWillAppear(_ animated: Bool) { 21 22 UserDefaults.standard.array(forKey: "TimeTable") 23 if let array: [Class] = UserDefaults.standard.array(forKey: "TimeTable") as? [Class] { 24 ViewController.testArray = array 25 print(array) 26 } else { 27 print("Error") 28 } 29 30 collectionView.reloadData() 31 } 32 33 34 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 35 36 ViewController.cellNumber = indexPath.row 37 38 if ViewController.cellNumber == indexPath.row { 39 print("セル番号:(indexPath.row)") 40 } 41 42 performSegue(withIdentifier: "Modal1", sender: self) 43 } 44 45 46 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 47 let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell 48 cell.backgroundColor = UIColor.groupTableViewBackground 49 50 for data in ViewController.testArray { 51 if indexPath.row == data.cellNumber { 52 cell.classLabel.text = data.className 53 cell.roomLabel.text = data.roomName 54 } else { 55 cell.classLabel.text = "" 56 cell.roomLabel.text = "" 57 } 58 } 59 return cell 60 } 61 62} 63

試したこと

完了ボタンを押してUserDefaultsで保存する時にエラーが出ていると予想しましたが、対処法がわかりません。
ViewController.testArrayがClass型だから保存できないのでしょうか?

補足情報(FW/ツールのバージョンなど)

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

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

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

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

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

guest

回答1

0

ベストアンサー

ViewController.testArrayがClass型だから保存できないのでしょうか?

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

先日も同じような質問(カスタムクラスの配列を保存する)に答えていますので、以下を参考にしてみてください。

参考質問: ランキング表示がうまくいきません。Swift,配列,Double型
参考サイト(古いですが): NSUserDefaultsに自作クラスを保持(swift)

回答追記

取得するところのコードが間違えていたので修正、あとクラス方の初期化のところもdecodeIntegerを使うように修正しました。
違いを比べて自分のプロジェクトに当てはめてみてください。

swift

1import UIKit 2 3class ViewController: UIViewController { 4 5 override func viewDidLoad() { 6 super.viewDidLoad() 7 8 let classData1 = Class(className: "a", roomName: "b", cellNumber: 1) 9 let classData2 = Class(className: "c", roomName: "d", cellNumber: 2) 10 let classData3 = Class(className: "e", roomName: "f", cellNumber: 3) 11 let classArray = [classData1, classData2, classData3] 12 13 // 保存 14 UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: classArray), forKey: "TimeTable") 15 UserDefaults.standard.synchronize() 16 17 // 取得 18 if let data = UserDefaults.standard.object(forKey: "TimeTable") as? Data, 19 let classArray = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Class] { 20 print(classArray) 21 } 22 } 23} 24 25class Class: NSObject, NSCoding { 26 27 let className: String 28 let roomName: String 29 let cellNumber: Int 30 31 init(className: String, roomName: String, cellNumber: Int) { 32 33 self.className = className 34 self.roomName = roomName 35 self.cellNumber = cellNumber 36 } 37 38 func encode(with aCoder: NSCoder) { 39 aCoder.encode(self.className, forKey: "name") 40 aCoder.encode(self.roomName, forKey: "room") 41 aCoder.encode(self.cellNumber, forKey: "number") 42 } 43 44 required init?(coder aDecoder: NSCoder) { 45 self.className = aDecoder.decodeObject(forKey: "name") as! String 46 self.roomName = aDecoder.decodeObject(forKey: "room") as! String 47 self.cellNumber = aDecoder.decodeInteger(forKey: "number") 48 } 49}

投稿2018/05/06 21:09

編集2018/05/07 21:21
_Kentarou

総合スコア8490

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

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

s1209

2018/05/07 14:04 編集

ありがとうございます。 コードを書いてみたのですが、UserDefaultsで配列(ViewController.testArray)を保存できていないようです。。。 保存する時のコードが間違っているのでしょうか? ``` class Class: NSObject, NSCoding { let className: String let roomName: String let cellNumber: Int init(className: String, roomName: String, cellNumber: Int) { self.className = className self.roomName = roomName self.cellNumber = cellNumber } func encode(with aCoder: NSCoder) { aCoder.encode(self.className, forKey: "name") aCoder.encode(self.roomName, forKey: "room") aCoder.encode(self.cellNumber, forKey: "number") } required init?(coder aDecoder: NSCoder) { self.className = aDecoder.decodeObject(forKey: "name") as! String self.roomName = aDecoder.decodeObject(forKey: "room") as! String self.cellNumber = aDecoder.decodeObject(forKey: "number") as! Int } } ``` ``` class Edit: UIViewController, UITextFieldDelegate { // 完了ボタンを押した時 @IBAction func tappedButton(_ sender: UIButton) { let classData = Class(className:classTextField.text!, roomName: roomTextField.text!, cellNumber: ViewController.cellNumber) ViewController.testArray.append(classData) // 保存 UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: ViewController.testArray), forKey: "TimeTable") UserDefaults.standard.synchronize() print(UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: ViewController.testArray), forKey: "TimeTable")) dismiss(animated: true) {} } } ``` ``` class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { static var testArray: [Class] = [] override func viewWillAppear(_ animated: Bool) { // 取得 let userDefault = UserDefaults.standard.array(forKey: "TimeTable") if let array = userDefault { let myClass = NSKeyedUnarchiver.unarchiveObject(with: (userDefault as! NSData) as Data) as! Class print(array) } else { print("Error") } collectionView.reloadData() } } ```
_Kentarou

2018/05/07 21:22

コードに間違えている部分があったので、回答の方に記載しました。
s1209

2018/05/08 09:26

ありがとうございます。 取得コードを書き直してみました。 ``` // 取得 if let data = UserDefaults.standard.object(forKey: "TimeTable") as? Data, let array = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Class] { print("取得:(array)") } ```
_Kentarou

2018/05/08 11:59

値が取得でき、解決したということでしょうか?
s1209

2018/05/09 07:28

無事に取得できました! 本当にありがとうございました。
s1209

2018/05/09 12:59

解決済み後にコメントしてしまい大変申し訳ないのですが、xcodeのシミュレーターを起動し、値の入力をした後にシミュレーターを終了、再度シミュレーターを起動すると入力した値が消えてしまうため、うまく保存ができていなかったようです。。。 取得部分のprintではarrayがちゃんと出力されるので、解決したと思い込んでしまいました。 再起動するとarrayの中身が初期化されるので保存ができていないということでしょうか? 私の確認不足でご迷惑おかけしてすみません。
_Kentarou

2018/05/09 13:53

> 取得部分のprintではarrayがちゃんと出力される でしたら、保存はできていると思いますね、空で上書き等していないでしょうか? 後は直接値が入っているか確認してみるのもいいと思います。 https://qiita.com/dondoko-susumu/items/85f5e1449df94be20fa9
s1209

2018/05/13 06:27

返信が遅くなり申し訳ありません。 おそらく上書きはしていないと思います。 参考記事のやり方で値が入っているか確認してみようとしましたが、find ./ ...の後に`-bash: com.~.timeTableApp: No such file or directory`と出てしまいます。
_Kentarou

2018/05/13 06:50

もっとシンプルなプロジェクトを作成して(今回私が書いた程度)、動くことを確認してから自分のプロジェクトに移してみてください。 そうすればどこが悪いかあたりがつけやすいと思いますので。
s1209

2018/05/13 06:59

ありがとうございます。 上記の方法でやってみます。
_Kentarou

2018/05/13 07:12

それでも分からなければまた聞いてください。
s1209

2018/05/13 07:59

なぜNo such file or directoryが出るか分かりました。 この方法だと実機を接続しないとダメなんですね。。。 MacBookProのUSB-Cに対応したケーブルを持っていないので、この方法で確認できない状況です(近々購入します)
_Kentarou

2018/05/13 08:09 編集

実機でなくてもいけるはずですが、状況がわからないので、、、 実機を繋いでいるのでしたら古い記事ですが、以下の方法でアプリの中の情報にアクセスできます。 UserDefaultsも格納されているのでみてみてください。 https://qiita.com/snaka/items/f10994af66549a4c2c5a
s1209

2018/05/13 09:22 編集

てっきり実機がないとダメだと思ったのですが、古い記事だったんですね。 いろいろ試してみたのですが、シミュレーター再起動してもUserDefaults取得時のprint(array)の内容は保存されたままなので、セルに表示させるところ(cellForItemAt)が間違っていると推測しました。 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell cell.backgroundColor = UIColor.groupTableViewBackground if let data = ViewController.testArray.first(where: { $0.cellNumber == indexPath.row }) { cell.classLabel.text = data.className cell.roomLabel.text = data.roomName } else { cell.classLabel.text = "" cell.roomLabel.text = "" } return cell }
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問