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

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

ただいまの
回答率

87.48%

CoreDataを使ってデータを保存する際、前画面から値が引き渡された場合とそうでない場合の場合分けをしたい

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,001

score 19

はじめに

初めまして。swiftを勉強して二ヶ月ほどの初心者です。
今回勉強がてら簡単なメモアプリを作成しているんですが、データ保存にcoreDataを使ってみたところ
なかなかうまくいかなくなってしまったのでご教授願いします。

メモアプリの概要

就活生用のエントリーシートメモアプリを作成しています。
保存するデータは

  • メモのタイトル
  • 企業名
  • 提出期限名
  • メモテキスト
  • テキストの文字数

です。

CoreDataも以下のように設定しました。
イメージ説明

困っている部分

メモの新規作成の場合と編集の場合を、前の画面から引き渡された値があるかないかで場合分けしたいのですが、うまくできません。

該当するコード

//
//  ViewController.swift


import UIKit
import CoreData

class ViewController: UIViewController, UITextFieldDelegate,UITextViewDelegate {

    @IBOutlet var titleField: UITextField!
    @IBOutlet var memoTextView: UITextView!
    @IBOutlet var memoNumLabel: UILabel!
    @IBOutlet var companyField: UITextField!
    @IBOutlet var dateField: UITextField!

    //coreData
    var memo:Memo?

    //UIDatePickerを定義するための変数
    var datePicker: UIDatePicker = UIDatePicker()

    var context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

    //画面が呼ばれる前
    override func viewDidAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        print(memo)
    }

    @IBAction func tapScreen(_ sender: Any) {
        self.view.endEditing(true)
    }
    //画面が呼ばれた時
    override func viewDidLoad() {
        super.viewDidLoad()
        titleField.delegate = self
        memoTextView.delegate =  self
        companyField.delegate = self
        dateField.delegate = self

        if let memo = memo {
            editedMemo(memo)
        }

        // ピッカー設定
        datePicker.datePickerMode = UIDatePicker.Mode.dateAndTime
        datePicker.timeZone = NSTimeZone.local
        datePicker.locale = Locale.current
        dateField.inputView = datePicker

        // 決定バーの生成
        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 35))
        let spacelItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        let doneItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: Selector(("doneBtn")))
        toolbar.setItems([spacelItem, doneItem], animated: true)

        // インプットビュー設定(紐づいているUITextfieldへ代入)
        dateField.inputView = datePicker
        dateField.inputAccessoryView = toolbar
        //キーボードを閉じる
        view.endEditing(true)
    }

    // UIDatePickerのDoneを押したら発火
    @objc func doneBtn() {
        dateField.endEditing(true)

        // 日付のフォーマット
        let formatter = DateFormatter()

        //"yyyy年MM月dd日"を"yyyy/MM/dd"したりして出力の仕方を好きに変更できる
        formatter.dateFormat = "yyyy年MM月dd日H時"

        //(from: datePicker.date))を指定してあげることで
        //datePickerで指定した日付が表示される
        dateField.text = "\(formatter.string(from: datePicker.date))"

    }

    //入力ごとに文字数をカウントする
    func textViewDidChange(_ textView: UITextView) {
        let commentNum = memoTextView.text.count
        memoNumLabel.text = String(commentNum)
    }

    func editedMemo(_ memo:Memo){
        //編集用に表示
        titleField.text = memo.title
        companyField.text = memo.company
        memoTextView.text = memo.memoText
        memoNumLabel.text = memo.memoNum
        dateField.text = memo.memoDate

    }

    @IBAction func saveMemo(_ sender: Any) {
        //alertの設定
        let alert: UIAlertController = UIAlertController(title: "メモの登録", message: "この内容で保存しますか?", preferredStyle:  UIAlertController.Style.alert)

        // キャンセルボタン
        let cancelAction: UIAlertAction = UIAlertAction(title: "キャンセル", style: UIAlertAction.Style.cancel, handler:{
            // ボタンが押された時の処理を書く(クロージャ実装)
            (action: UIAlertAction!) -> Void in
            print("Cancel")
        })


        // OKボタン押下時のイベント
        let okAction = UIAlertAction(title: "OK", style: .default) { (action) in

            let editTitle = self.titleField.text
            let editCompany = self.companyField.text
            let editText = self.memoTextView.text
            let editNum = self.memoNumLabel.text
            let editDate = self.dateField.text


            if self.memo == nil {
                let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
                self.memo = Memo(context: context)
            }

            if let memo = self.memo{
                // context(データベースを扱うのに必要)を定義。
                let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

                let appDelegate = UIApplication.shared.delegate as! AppDelegate
                let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Memo")
                let predicate = NSPredicate(format: "title = %@ and company = %@ and memoText = %@ and memoNum = %@ and memoDate = %@", memo.title!, memo.company!, memo.memoText!, memo.memoNum!, memo.memoDate!)
                fetchRequest.predicate = predicate

                do {
                    let memo = try context.fetch(fetchRequest)
                    let memoData:Memo = memo[0] as! Memo
                    memoData.title = self.titleField.text
                    memoData.company = self.companyField.text
                    memoData.memoText = self.memoTextView.text
                    memoData.memoNum = self.memoNumLabel.text
                    memoData.memoDate = self.dateField.text

                } catch let error as NSError {
                    print(error)
                }

            }

            // 上で作成したデータをデータベースに保存
            (UIApplication.shared.delegate as! AppDelegate).saveContext()

            self.dismiss(animated: true, completion: nil)

            //入力値をクリアにする
            self.clearData()
        }

        func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }

        alert.dismiss(animated: true, completion: nil)

        // ③ UIAlertControllerにActionを追加
        alert.addAction(cancelAction)
        alert.addAction(okAction)

        // ④ Alertを表示
        present(alert, animated: true, completion: nil)

    }
    // 入力値をクリア
    func clearData()  {
        titleField.text = ""
        companyField.text = ""
        memoTextView.text = ""
        memoNumLabel.text = "0"
        dateField.text = ""
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        //キーボードを隠す
        textField.resignFirstResponder()
        return true
    }

    func applicationWillTerminate(_ application: UIApplication) {
        self.saveContext()
    }
    //データを保存
    func saveContext () {

        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
                print(context)
            } catch {
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}

エラー内容

「Unexpectedly found nil while unwrapping an Optional value」
と言われます。

きっと

if self.memo == nil {
                let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
                self.memo = Memo(context: context)
            }


の部分でmemoに値が代入されるために
次のコード

if let memo = self.memo{
                // context(データベースを扱うのに必要)を定義。
                let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

                let appDelegate = UIApplication.shared.delegate as! AppDelegate
                let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Memo")
                let predicate = NSPredicate(format: "title = %@ and company = %@ and memoText = %@ and memoNum = %@ and memoDate = %@", memo.title!, memo.company!, memo.memoText!, memo.memoNum!, memo.memoDate!)
                fetchRequest.predicate = predicate
//以下省略


の部分が通れてしまっているのが原因だと考えています。
そこで if{}を2つ並べるのではなく、
if{}else{}としてみたものの新たなエラーがでます。

 if self.memo == nil {
                let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
                self.memo = Memo(context: context)

            }else{
                let memo = self.memo
                // context(データベースを扱うのに必要)を定義。
                let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

                let appDelegate = UIApplication.shared.delegate as! AppDelegate
                let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Memo")
                let predicate = NSPredicate(format: "title = %@ and company = %@ and memoText = %@ and memoNum = %@ and memoDate = %@", memo.title!, memo.company!, memo.memoText!, memo.memoNum!, memo.memoDate!)
                fetchRequest.predicate = predicate

                do {
                    let memo = try context.fetch(fetchRequest)
                    let memoData:Memo = memo[0] as! Memo
                    memoData.title = self.titleField.text
                    memoData.company = self.companyField.text
                    memoData.memoText = self.memoTextView.text
                    memoData.memoNum = self.memoNumLabel.text
                    memoData.memoDate = self.dateField.text

                } catch let error as NSError {
                    print(error)
                }

            }


と変更すると、
イメージ説明
というエラーがでてしまい、これを解決することもできずに行き詰まってしまっています、、、。

どうにか助けていただきたいです。よろしくお願いします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

0

考え方から変えましょう。

あなたは保存するときにはじめて新規作成だった時に空のMemoを生成しようとしていますが、ビューが表示されるときにやってしまいましょう。

例えばこうです

override func viewDidLoad() {
    ...
    ...
    // 削除
//    if let memo = memo {
//        editedMemo(memo)
//    }

    // メモがなければ新規作成
    if memo == nil {
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        memo = Memo(context: context)
    }

    // メモの値を表示。
    editedMemo(memo)

こうしておけば、保存時に新規作成か編集かの判定をする必要はなく、単にmemoの保存処理だけをすればよいようになります。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/02/26 03:34

    無事解決しました!ありがとうございました!

    キャンセル

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

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

関連した質問

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

  • トップ
  • Xcodeに関する質問
  • CoreDataを使ってデータを保存する際、前画面から値が引き渡された場合とそうでない場合の場合分けをしたい