見出し画像

【徒然DB】気ままにUIKit-CoreData編5〜外部ファイルに保存されない一時的な属性を定義〜

概要

このマガジンは四十を過ぎたおっさんが、

を参考にStoryboardでiOSアプリを完全に趣味で楽しんでいるだけな記事を気ままに上げてます。

今回

をハイ、レッツゴ🕺

前準備

念の為、

  1. バックアップ

  2. 新規クラスを追加

  3. メニュービューと新規ビューを追加

  4. 今回用の事前準備

をやってから本題へ💃

てな感じで、テストプロジェクトを参考に作ってみた💦

ここで〜〜〜

テストプロジェクトの方は、前回使わんなあ〜〜
って言っていた、Constraintsをnameでやってしまっていて、
かなりな影響度が他のビューにまで及んで、

なんかでデータモデルのバージョンを管理しないとCoreDataと連携してる全てのビューが、Playerエンティティ内のnameデータがまっさらでない限り、開けなくなる様子
👉影響度合いがデカ過ぎて、このプロジェクトの趣旨に反しまくってくるので。

//
//  Player+CoreDataProperties.swift
//
import Foundation
import CoreData
extension Player {

    @NSManaged var name: String?
    @NSManaged var age: NSNumber?
    @NSManaged var adult: NSNumber?

}

//
//  ViewController.swift
//
import UIKit
import CoreData
class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var testLabel: UILabel!
    @IBOutlet weak var testTextField1: UITextField!
    @IBOutlet weak var testTextField2: UITextField!
    //管理オブジェクトコンテキスト
    var managedContext:NSManagedObjectContext!
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //管理オブジェクトコンテキストを取得する。
        let applicationDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        managedContext = applicationDelegate.managedObjectContext
        //managedContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        //保存データを表示する。
        displayData()
        //デリゲート先に自分を設定する。
        testTextField1.delegate = self
        testTextField2.delegate = self
    }
    //保存データ表示メソッド
    func displayData(){
        do {
            //Playエンティティで保存されているデータを取得する。
            let fetchRequest = NSFetchRequest(entityName: "Player")
            let result = try managedContext.executeFetchRequest(fetchRequest) as! [Player]
            //取得したデータをラベルに表示する。
            var outputStr = ""
            for data in result {
                if let name = data.name, age = data.age {
                    outputStr = outputStr + "," + name + "," + String(age)
                }
            }
            testLabel.text = outputStr
        } catch {
            print(error)
        }
    }
    //Returnキー押下時の呼び出しメソッド
    func textFieldShouldReturn(textField:UITextField) -> Bool {
        //キーボードをしまう
        self.view.endEditing(true)
        return true
    }
    //ボタン押下時の呼び出しメソッド
    @IBAction func pushButton(sender: UIButton) {
        do {
            //nameの値が同じオブジェクトを検索する。
            let fetchRequest = NSFetchRequest(entityName: "Player")
            fetchRequest.predicate = NSPredicate(format: "name = %@", testTextField1.text!)
            let players = try managedContext.executeFetchRequest(fetchRequest) as! [Player]
            if (players.count == 0) {
                //検索にヒットしなかった場合は新しいオブジェクトを管理オブジェクトコンテキストに格納する。
                let player = NSEntityDescription.insertNewObjectForEntityForName("Player", inManagedObjectContext: managedContext) as! Player
                player.name = testTextField1.text!
                player.age = Int(testTextField2.text!)
            } else if(players.count == 1) {
                //検索にヒットした場合は、そのオブジェクトを更新する。
                players.first!.age = Int(testTextField2.text!)
            } else {
                print("想定外")
            }
            //管理オブジェクトコンテキストに格納したデータを保存する。
            try managedContext.save()
            //データを表示する。
            displayData()
        } catch {
            print(error)
        }
    }
}

てコードは、

extension Player {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<Player> {
        return NSFetchRequest<Player>(entityName: "Player")
    }

    @NSManaged public var age: Int16
    @NSManaged public var name: String?
    @NSManaged var adult: NSNumber?

}

extension Player : Identifiable {

}

class TemporaryAttributeCoreDataViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet weak var myAgeTextField: UITextField!
    @IBOutlet weak var myNameTextField: UITextField!
    
    //管理オブジェクトコンテキスト
    var managedContext:NSManagedObjectContext!
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //管理オブジェクトコンテキストを取得する。
        let applicationDelegate = UIApplication.shared.delegate as! AppDelegate
        managedContext = applicationDelegate.persistentContainer.viewContext
        //保存データを表示する。
        displayData()
        //デリゲート先に自分を設定する。
        myNameTextField.delegate = self
        myAgeTextField.delegate = self
    }
    //保存データ表示メソッド
    func displayData(){
        do {
            //Playエンティティで保存されているデータを取得する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            let result = try managedContext.fetch(fetchRequest) as! [Player]
            //取得したデータをラベルに表示する。
            var outputStr = ""
            for data in result {
                myLabel.text = outputStr + "," + data.name! + "," + String(describing: data.age)
            }
        } catch {
            print(error)
        }
    }
    //Returnキー押下時の呼び出しメソッド
    func textFieldShouldReturn(_ textField:UITextField) -> Bool {
        //キーボードをしまう
        self.view.endEditing(true)
        return true
    }
    //ボタン押下時の呼び出しメソッド
    @IBAction func myPush(_ sender: UIButton) {
        do {
            //nameの値が同じオブジェクトを検索する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            //検索にヒットしなかった場合は新しいオブジェクトを管理オブジェクトコンテキストに格納する。
            let player = NSEntityDescription.insertNewObject(forEntityName: "Player", into: managedContext) as! Player
            player.name = myNameTextField.text!
            player.age = Int16(myAgeTextField.text!)!
            //管理オブジェクトコンテキストに格納したデータを保存する。
            try managedContext.save()
            //データを表示する。
            displayData()
        } catch {
            print(error)
        }
    }
}

てな感じで、nameを一意制約を付けてない
👇

fetchRequest.predicate = NSPredicate(format: "name = %@", testTextField1.text!)
let players = try managedContext.executeFetchRequest(fetchRequest) as! [Player]
if (players.count == 0) {

} else if(players.count == 1) {
  //検索にヒットした場合は、そのオブジェクトを更新する。
   players.first!.age = Int(testTextField2.text!)
} else {
  print("想定外")
}

の部分は大幅に削除したり、

for data in result {
  if let name = data.name, age = data.age {
      outputStr = outputStr + "," + name + "," + String(age)
    }
}

も特に無条件でラベルに表示できるように変更した〜〜〜💦

てな感じで、ここまで書けばわかると思うけど、

サイト記事のサンプルプロジェクトとはかけ離れて、
既にかなりややこしいことになっている

内容を見た感じ。
ただ単に、

テストって値を一時エンティティを作って、
プロパティに追加すればいいだけ〜〜〜

って感じなので、
後はサイト記事の重要項目などを追って簡単に済ませることにしまする〜〜〜

本題

ま、要はこのデータモデルの
ここの設定の話ね👀
  • Name:属性の名前。

  • Transient:チェックを入れると、外部ファイルに保存されない一時的な属性になる。

てことね👀

⒈Attributeにtestを追加して、Tranisientにチェックを入れる

こんな感じで

⒉Player+CoreDataProperties.swiftファイルに、プロパティを追加

てな感じで〜〜〜

⒊コード組み込み

class TemporaryAttributeCoreDataViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet weak var myAgeTextField: UITextField!
    @IBOutlet weak var myNameTextField: UITextField!
    
    //管理オブジェクトコンテキスト
    var managedContext:NSManagedObjectContext!
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //管理オブジェクトコンテキストを取得する。
        let applicationDelegate = UIApplication.shared.delegate as! AppDelegate
        managedContext = applicationDelegate.persistentContainer.viewContext
        //保存データを表示する。
        displayData()
        //デリゲート先に自分を設定する。
        myNameTextField.delegate = self
        myAgeTextField.delegate = self
    }
    //保存データ表示メソッド
    func displayData(){
        do {
            //Playエンティティで保存されているデータを取得する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            let result = try managedContext.fetch(fetchRequest) as! [Player]
            //取得したデータをラベルに表示する。
            var outputStr = ""
            for data in result {
                myLabel.text = outputStr + "," + data.name! + "," + String(describing: data.age) + "," + String(data.test)
            }
        } catch {
            print(error)
        }
    }
    //Returnキー押下時の呼び出しメソッド
    func textFieldShouldReturn(_ textField:UITextField) -> Bool {
        //キーボードをしまう
        self.view.endEditing(true)
        return true
    }
    //ボタン押下時の呼び出しメソッド
    @IBAction func myPush(_ sender: UIButton) {
        do {
            //nameの値が同じオブジェクトを検索する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            let players = try managedContext.fetch(fetchRequest) as! [Player]
            //検索にヒットしなかった場合は新しいオブジェクトを管理オブジェクトコンテキストに格納する。
            let player = NSEntityDescription.insertNewObject(forEntityName: "Player", into: managedContext) as! Player
            player.name = myNameTextField.text!
            player.age = Int16(myAgeTextField.text!)!
            player.test = 777
            //管理オブジェクトコンテキストに格納したデータを保存する。
            try managedContext.save()
            //データを表示する。
            displayData()
        } catch {
            print(error)
        }
    }
}

に変えてみて〜〜〜〜

⒋シミュレータで実行

ハイ、完了🕺

⒌コードを一部コメントアウト

ここをコメントアウトして〜〜〜
開き直すと、777は0になってるけども👀
実は、他の画面で開くと、
保存できてんだよね💦

サイト記事の内容については、以上👀

ブラッシュアップ

意味がないので、コメントアウトのコードは元に戻す

戻した

今回のコード(まとめ)

class TemporaryAttributeCoreDataViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var myLabel: UILabel!
    @IBOutlet weak var myAgeTextField: UITextField!
    @IBOutlet weak var myNameTextField: UITextField!
    
    //管理オブジェクトコンテキスト
    var managedContext:NSManagedObjectContext!
    //最初からあるメソッド
    override func viewDidLoad() {
        super.viewDidLoad()
        //管理オブジェクトコンテキストを取得する。
        let applicationDelegate = UIApplication.shared.delegate as! AppDelegate
        managedContext = applicationDelegate.persistentContainer.viewContext
        //保存データを表示する。
        displayData()
        //デリゲート先に自分を設定する。
        myNameTextField.delegate = self
        myAgeTextField.delegate = self
    }
    //保存データ表示メソッド
    func displayData(){
        do {
            //Playエンティティで保存されているデータを取得する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            let result = try managedContext.fetch(fetchRequest) as! [Player]
            //取得したデータをラベルに表示する。
            var outputStr = ""
            for data in result {
                myLabel.text = outputStr + "," + data.name! + "," + String(describing: data.age) + "," + String(data.test)
            }
        } catch {
            print(error)
        }
    }
    //Returnキー押下時の呼び出しメソッド
    func textFieldShouldReturn(_ textField:UITextField) -> Bool {
        //キーボードをしまう
        self.view.endEditing(true)
        return true
    }
    //ボタン押下時の呼び出しメソッド
    @IBAction func myPush(_ sender: UIButton) {
        do {
            //nameの値が同じオブジェクトを検索する。
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Player")
            let players = try managedContext.fetch(fetchRequest) as! [Player]
            //検索にヒットしなかった場合は新しいオブジェクトを管理オブジェクトコンテキストに格納する。
            let player = NSEntityDescription.insertNewObject(forEntityName: "Player", into: managedContext) as! Player
            player.name = myNameTextField.text!
            player.age = Int16(myAgeTextField.text!)!
            player.test = 777
            //管理オブジェクトコンテキストに格納したデータを保存する。
            try managedContext.save()
            //データを表示する。
            displayData()
        } catch {
            print(error)
        }
    }
}

地球儀ボタン追加

てな感じで〜〜〜

記事公開後、

ハイ、完了💃
実機も問題なし🕺

Apple公式

さて、次回は

をレッツゴする🕺

ここら辺りから、完全に内容が乖離していきそうな内容なので〜〜〜
サイト記事をあくまでも参考に、手順どおりに作ってみた結果をまとめてく感じになると思う〜〜〜〜💦

ちなみに

一意制約を

てな感じで実行すると〜〜〜
エラーメッセージの一部だけど、
persistentContainer: NSPersistentContainerに致命的なエラーが発生してる〜〜〜
て感じで、Abstract Entityのチェックを外して〜〜〜

実行しても、

同じ事象

なので、冒頭に、一意制約はこのプロジェクトには向かないと書いた次第💦

まあ、深く数日かけてとかで調べればわかるんだろうけど、
そこまでしても、
このプロジェクトファイルのコンセプトと反するからね〜〜〜👀
あくまでも、サイト記事に沿って、できる機能をまとめてるだけだから。


この記事が気に入ったらサポートをしてみませんか?