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

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

ただいまの
回答率

90.49%

  • Swift

    7262questions

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

  • Xcode

    4109questions

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

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 307

s1209

score 5

 前提・実現したいこと

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'

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

import UIKit

class Class {
    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
    }
}

class Edit: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var classTextField: UITextField!
    @IBOutlet weak var roomTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()

        classTextField.delegate = self
        roomTextField.delegate = self

        let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGesture))
        self.view.addGestureRecognizer(tapRecognizer)
    }

    @objc func tapGesture() {
        classTextField.resignFirstResponder()
        roomTextField.resignFirstResponder()
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        classTextField.resignFirstResponder()
        roomTextField.resignFirstResponder()

        return true
    }


//    完了ボタンを押した時
    @IBAction func tappedButton(_ sender: UIButton) {

        let classData = Class(className:classTextField.text!, roomName: roomTextField.text!, cellNumber: ViewController.cellNumber)

        ViewController.testArray.append(classData)
        UserDefaults.standard.set(ViewController.testArray, forKey: "TimeTable")
        UserDefaults.standard.synchronize()

        print(ViewController.testArray.count)

        dismiss(animated: true) {}
    }

}
import UIKit

class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout  {

    static var cellNumber: Int = 0
    @IBOutlet weak var collectionView: UICollectionView!

    static var testArray: [Class] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        collectionView.delegate = self
        collectionView.dataSource = self

        collectionView.register(UINib(nibName: "CollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "CollectionViewCell")
    }

    override func viewWillAppear(_ animated: Bool) {

        UserDefaults.standard.array(forKey: "TimeTable")
        if let array: [Class] = UserDefaults.standard.array(forKey: "TimeTable") as? [Class] {
            ViewController.testArray = array
            print(array)
        } else {
            print("Error")
        }

        collectionView.reloadData()
    }


    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        ViewController.cellNumber = indexPath.row

        if ViewController.cellNumber == indexPath.row {
            print("セル番号:\(indexPath.row)")
        }

        performSegue(withIdentifier: "Modal1", sender: self)
    }


    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
        cell.backgroundColor = UIColor.groupTableViewBackground

        for data in ViewController.testArray {
        if indexPath.row == data.cellNumber {
            cell.classLabel.text = data.className
            cell.roomLabel.text = data.roomName
        } else {
            cell.classLabel.text = ""
            cell.roomLabel.text = ""
        }
        }
        return cell
    }

}

 試したこと

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+3

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

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

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

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

回答追記

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

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let classData1 = Class(className: "a", roomName: "b", cellNumber: 1)
        let classData2 = Class(className: "c", roomName: "d", cellNumber: 2)
        let classData3 = Class(className: "e", roomName: "f", cellNumber: 3)
        let classArray = [classData1, classData2, classData3]

        // 保存
        UserDefaults.standard.set(NSKeyedArchiver.archivedData(withRootObject: classArray), forKey: "TimeTable")
        UserDefaults.standard.synchronize()

        // 取得
        if let data = UserDefaults.standard.object(forKey: "TimeTable") as? Data,
            let classArray = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Class] {
            print(classArray)
        }
    }
}

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.decodeInteger(forKey: "number")
    }
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/07 23:01 編集

    ありがとうございます。

    コードを書いてみたのですが、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()
    }
    }
    ```

    キャンセル

  • 2018/05/08 06:22

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

    キャンセル

  • 2018/05/08 18:26

    ありがとうございます。
    取得コードを書き直してみました。

    ```
    // 取得
    if let data = UserDefaults.standard.object(forKey: "TimeTable") as? Data, let array = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Class] {
    print("取得:\(array)")
    }
    ```

    キャンセル

  • 2018/05/08 20:59

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

    キャンセル

  • 2018/05/09 16:28

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

    キャンセル

  • 2018/05/09 21:59

    解決済み後にコメントしてしまい大変申し訳ないのですが、xcodeのシミュレーターを起動し、値の入力をした後にシミュレーターを終了、再度シミュレーターを起動すると入力した値が消えてしまうため、うまく保存ができていなかったようです。。。

    取得部分のprintではarrayがちゃんと出力されるので、解決したと思い込んでしまいました。
    再起動するとarrayの中身が初期化されるので保存ができていないということでしょうか?

    私の確認不足でご迷惑おかけしてすみません。

    キャンセル

  • 2018/05/09 22:53

    > 取得部分のprintではarrayがちゃんと出力される

    でしたら、保存はできていると思いますね、空で上書き等していないでしょうか?

    後は直接値が入っているか確認してみるのもいいと思います。
    https://qiita.com/dondoko-susumu/items/85f5e1449df94be20fa9

    キャンセル

  • 2018/05/13 15:27

    返信が遅くなり申し訳ありません。
    おそらく上書きはしていないと思います。

    参考記事のやり方で値が入っているか確認してみようとしましたが、find ./ ...の後に`-bash: com.~.timeTableApp: No such file or directory`と出てしまいます。

    キャンセル

  • 2018/05/13 15:50

    もっとシンプルなプロジェクトを作成して(今回私が書いた程度)、動くことを確認してから自分のプロジェクトに移してみてください。

    そうすればどこが悪いかあたりがつけやすいと思いますので。

    キャンセル

  • 2018/05/13 15:59

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

    キャンセル

  • 2018/05/13 16:12

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

    キャンセル

  • 2018/05/13 16:59

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

    キャンセル

  • 2018/05/13 17:09 編集

    実機でなくてもいけるはずですが、状況がわからないので、、、

    実機を繋いでいるのでしたら古い記事ですが、以下の方法でアプリの中の情報にアクセスできます。
    UserDefaultsも格納されているのでみてみてください。

    https://qiita.com/snaka/items/f10994af66549a4c2c5a

    キャンセル

  • 2018/05/13 18: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
    }

    キャンセル

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

  • ただいまの回答率 90.49%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • Swift

    7262questions

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

  • Xcode

    4109questions

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