Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
84
Help us understand the problem. What are the problem?

posted at

updated at

Organization

やっとわかったSwift/CoreData入門 【part2:とりあえず実装編】

スクリーンショット 2018-12-23 17.57.04.png

🍀まえがき

■書いてあること

こちらの記事からの続編
👨‍💻やっとわかったSwift/CoreData入門 【part1:概念編】

CoreDataについて、必要最低限知っておくべきことを、プログラミング初心者の私が、私にとってわかりやすい感じで説明。
今回は必要最低限の実装をしてみます。

■下記の症状に効能があります

  • CoreDataについて調べてみたけど、情報すくねぇ
  • 記事とか見たけど、Swiftのバージョンが古かったり、記事によって書いてあることが違くて大混乱
  • 中級以上を対象とした説明ばかりで全くわからん
  • 何を言ってるのかチンプンカンプンだからもっとレベル下げて欲しい
  • とりあえず最低限の実装で基本を理解したい

■この記事の特徴

初心者でも内容が分かりやすいようになってます。
変数は英語で命名するのがセオリーですが、あえて全部日本語にしてます(それでも動きます)
xcode 10.1 swift 4.2 (2018年12月12日現在)

■主な流れ

  • プロジェクトの作成
  • CoreData用の変更箇所を確認
  • AppDelegate内のコードの解説
  • Entityの設定
  • ストーリーボードの設定
  • ViewControllerの実装
  • 完成!

🛠本編

◼️プロジェクトの作成

  • とりあえずSingleViewAppを選択
  • 今回のプロダクトネームは「TestCoreData」にしました
  • UseCoreDataにチェックを忘れずに!
  • *ちなみに、ここでチェックを入れなくても後から設定することも可能ですが、今回は省略 スクリーンショット 2018-12-16 17.45.12.png

◼️CoreData用の変更箇所を確認

  • プロジェクトを作成した際に、UseCoreDataにチェックを入れるとどのような変更があるかを確認しましょう
  • 主に下記の2点に変更があります
    1. AppDelegate.swiftにCoreData用のコードが追加される
    2. プロジェクトに[プロジェクト名].xcdatamodeldファイルが追加される

スクリーンショット 2018-12-16 17.49.44.png

◼️AppDelegate内のコードの解説

AppDelegate内のCoreDataの部分では何をやってるのでしょうか?コードを抜粋して解説します。

AppDelegate.swift
lazy var persistentContainer: NSPersistentContainer = {
  let container = NSPersistentContainer(name: "TestCoreData")
  container.loadPersistentStores(completionHandler: { (storeDescription, error) in
    if let error = error as NSError? {
      fatalError("Unresolved error \(error), \(error.userInfo)")
    }
  })
return container
}()

はい!僕はこの時点で見事にハマりました。特に最後の「 }() 」の部分なんやねん!っと。
少し複雑に見えますが、なんのことはなく、簡単にするとこういうことです。

//NSPersistentContainer型として、persistentContainer変数にcontainerをオブジェクト化してぶち込むよ!
var persistentContainer: NSPersistentContainer = container()

波括弧内がそのままメソッドの定義になっていて、それを叩いて代入してるだけなんですね!括弧が多くて惑わされますが、よく見てみましょう。

スクリーンショット 2018-12-23 18.48.09.png

中のコードも簡略化すると

A = {
  let container = NSPersistentContainer(name: "TestCoreData")
  container.loadPersistentStores(ごにょごにょとエラー処理する)
  return container
}

lazy var persistentContainer: NSPersistentContainer = A()

このように非常に単純なことをやってます。

文法はこちらを見ると良いと思います。
- クロージャーについて
- lazyについて

前回掲載した概念図に当てはめると、アプリを立ち上げた時に、図中のクラスセットをオブジェクト化するよーというイメージです

スクリーンショット 2018-12-16 18.22.51.png

次にこちらのコードを解説します。ここでは、実際にデータを保存するときのパスの設定を行ってます。

AppDelegate.swift
func saveContext () {
  let context = persistentContainer.viewContext
  if context.hasChanges {
    do {
      try context.save()
    } catch {
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
  }
}

まず、重要なのが一行目。先ほど作成したpersistentContainer内のviewContextメソッドを叩いて定数に突っ込んでます。ここがなかなかわかりにく。
要はこれは、persistentContainer内にラッピングされているNSManagedObjectContextをオブジェクト化しますよーということ。

AppDelegate.swift
//NSPersistentContainer内のNSManagedObjectContextを定数contextに代入
let context = persistentContainer.viewContext

  //NSManagedObjectContextに変更があったら、保存しますよ
  if context.hasChanges {
    do {
      //NSManagedObjectContextの内容を保存するよ
      try context.save()
    } catch {
      //なんかエラーがあったらエラーを吐くよ
      let nserror = error as NSError
      fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
  }
}

◼️Entityの設定

次は、Entityの設定です。
DBのテーブルを作る感じです。

  • TestCoreData.xcdatamodeldを選択後、AddEntityを選択するとEntityが追加されます。

スクリーンショット 2018-12-16 19.20.34.png

  • Entityの名前を「Monster」にしてみます。
  • Attributeを「monsterName」、データ型をStringとしました。

💡つまづきポイント
ここの設定が終わったら、一度buildしておきましょう。buildしないと後ほどEntityをコード内で呼び出す時にエラーが出ます*

スクリーンショット 2018-12-16 19.25.33.png

今回は簡単な実装にするため、設定はこれだけにしておきます。

◼️ストーリーボードの設定

  • view上部にテキストフィールドを配置します(ReturnKeyをDONEにしました)
  • テーブルビューとセルを設置します(セルのidentifireはMyCellにしました)
  • テーブルビューのdatasourceとdelegateを紐付けました
  • 今回はこの2パーツだけを使用します

*この辺の詳細はこちらの記事をご参照ください
👨‍💻SwiftでTableViewを使ってみよう

◼️ViewControllerの実装

先に完成品です

ViewController.swift
import UIKit
//CoreDataをインポート
import CoreData

//テキストフィールドデリゲートを追加
class ViewController: UIViewController,UITextFieldDelegate {

  //パーツの紐付け
  @IBOutlet weak var テキストフィールド: UITextField!
  @IBOutlet weak var テーブルビュー: UITableView!

  //EntityのMonster型の配列、モンスター変数を宣言
  //MonsterEntityから引っ張ってきたデータを入れるためMonseter型にしておく
  var モンスターオブジェクトの配列:[Monster] = []

  //NSManagedObjectContextをAppDelegate経由でオブジェクト化
  //(UIApplication.shared.delegate as! AppDelegate)はAppDelegateへのパスのようなもの
  //AppDelegateのpersistentContainerのviewContext(NSManagedObjectContextのこと)を叩いてオブジェクト化してるだけ
  var マネージドオブジェクトコンテキスト = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext


  override func viewDidLoad() {
    super.viewDidLoad()

    //テキストフィールドの処理先をViewController自身にしする
    テキストフィールド.delegate = self

    //NSFetchRequestを使って、「任意のEntityの全データを取得する」という取得条件を変数に打ち込む
    //<NSFetchRequestResult>の部分はジェネリックという文法らしい
    //NSFetchRequestResult型としてNSFetchRequestを処理しますよって感じ
    let 取得したいデータの条件 = NSFetchRequest<NSFetchRequestResult>(entityName: "Monster")
    do{
      //マネージドオブジェクトコンテキストのfetchに先ほどの取得条件を食わせて、返ってきたデータをMonster型に強制ダウンキャスト
      //取得したデータをモンスターオブジェクト変数に入れる
      モンスターオブジェクトの配列 = try マネージドオブジェクトコンテキスト.fetch(取得したいデータの条件) as! [Monster]
    }catch{
      print("エラーだよ")
    }
  }

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


    //フィールドで作ったマネージドオブジェクトコンテキスト内にMonster型のマネージドオブジェクトを作る
    let 新規モンスターオブジェクト = Monster(context: self.マネージドオブジェクトコンテキスト)

    //新規モンスターオブジェクトのmonsterNameプロパティにテキストフィールドのテキストを上書き
    新規モンスターオブジェクト.monsterName = テキストフィールド.text

    //モンスターオブジェクトの配列に新規モンスターオブジェクトを追加する
    //これで元々のデータに新規のデータが追加された状態になった
    self.モンスターオブジェクトの配列.append(新規モンスターオブジェクト)

    //appdelegateのsaveContextメソッドを使って保存
    //saveContextは現在のマネージドオブジェクトの変更内容をDBに反映する
    (UIApplication.shared.delegate as! AppDelegate).saveContext()

    //取得したデータでテーブルの内容をリロード
    テーブルビュー.reloadData()
    //テキストフィールドの中身は空にしておく
    textField.text = ""
    return true
  }
}

extension ViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return モンスターオブジェクトの配列.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath)
    let モンスター個別データ = モンスターオブジェクトの配列[indexPath.row]
    cell.textLabel?.text = モンスター個別データ.monsterName
    return cell
  }


}

フィールドとかのポイント

  • CoreDataをimportする
import UIKit
//CoreDataをインポート
import CoreData
  • DBから取得したデータを突っ込む変数を用意する
  • *Entity設定後にbuildしてないとエラーが出ますので要注意
//EntityのMonster型の配列、モンスター変数を宣言
//MonsterEntityから引っ張ってきたデータを入れるためMonseter型にしておく
var モンスターオブジェクトの配列:[Monster] = []
  • NSManagedObjectContextをviewController内で使えるようにオブジェクト化する
//NSManagedObjectContextをAppDelegate経由でオブジェクト化
//UIApplication.shared.delegate as! AppDelegateはAppDelegateへのパスのようなもの
var マネージドオブジェクトコンテキスト = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

分解すると

//AppDelegateへのパスのようなもの
//UIApplication.sharedはシングルトンでdelegateを呼ぶよーという意味
(UIApplication.shared.delegate as! AppDelegate)

//つまりこんな感じでAppDelegate内のpersistentContainer内のviewContextを呼びたいだけ
AppDelegate.persistentContainer.viewContext

スクリーンショット 2018-12-23 19.25.46.png

viewdidloadの中身のポイント

//NSFetchRequestを使って、「任意のEntityの全データを取得する」という取得条件を変数に打ち込む
//<NSFetchRequestResult>の部分はジェネリックという文法らしい
//NSFetchRequestResult型としてNSFetchRequestを処理しますよって感じ
let 取得したいデータの条件 = NSFetchRequest<NSFetchRequestResult>(entityName: "Monster")
  • fetchを叩いて、データを取得
//マネージドオブジェクトコンテキストのfetchに先ほどの取得条件を食わせて、返ってきたデータをMonster型に強制ダウンキャスト
//取得したデータをモンスターオブジェクトの配列に入れる
モンスターオブジェクトの配列 = try マネージドオブジェクトコンテキスト.fetch(取得したいデータの条件) as! [Monster]

スクリーンショット 2018-12-23 20.06.22.png

textFieldShouldReturnの中身のポイント

  • 新規に追加したいデータの変数を作る
//フィールドで作ったマネージドオブジェクトコンテキスト内にMonster型のマネージドオブジェクトを作る
let 新規モンスターオブジェクト = Monster(context: self.マネージドオブジェクトコンテキスト)
  • テキスドフィールドの内容を新規に作った変数に入れる
//新規モンスターオブジェクトのmonsterNameプロパティにテキストフィールドのテキストを上書き
新規モンスターオブジェクト.monsterName = テキストフィールド.text
  • モンスターオブジェクトの配列に新規モンスターオブジェクトを追加する
//モンスターオブジェクトの配列に新規モンスターオブジェクトを追加する
//これで元々のデータに新規のデータが追加された状態になった
self.モンスターオブジェクトの配列.append(新規モンスターオブジェクト)
  • マネージドオブジェクトの状態をDBに保存する
//appdelegateのsaveContextメソッドを使って保存
//saveContextは現在のマネージドオブジェクトの変更内容をDBに反映する
(UIApplication.shared.delegate as! AppDelegate).saveContext()

スクリーンショット 2018-12-23 20.22.24.png

📱完成!

お疲れ様でした!
完成したら、コメントに「ワッフルワッフル」と叫んでください😆✨

📱📱📱サンプルはこちら📱📱📱
https://github.com/TD3P/TestCoreData

📗参考資料

http://glassonion.hatenablog.com/entry/20111015/1318667971
https://qiita.com/Saayaman/items/ea437032afaeddd0cf50
https://hajihaji-lemon.com/smartphone/swift/coredata/

🌟ご指摘大歓迎🌟

正直、全然理解できてないです。
ここちゃうで!!っていうとこあったら、教えてもらえると嬉しいです!!

NextStage!

🎉TODOアプリを作ってみようシリーズ

📱SwiftでTableViewを使ってみよう
https://qiita.com/TD3P/items/cafa8e20029047993025

📱Swiftで簡単なTODOアプリを作ってみよう
https://qiita.com/TD3P/items/8f474358d1dd789557f3

📱Swiftでカスタムセルを再利用したTODOアプリを作ってみよう
https://qiita.com/TD3P/items/116a2199b1f872ac6471

📱SwiftでCoreDataを使ったTODOアプリを作ってみよう
https://qiita.com/TD3P/items/adbbeee827995cffd509

📱SwiftでRealmを使ったTODOアプリを作ってみよう
https://qiita.com/TD3P/items/616e0dbd364179ca284b

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
84
Help us understand the problem. What are the problem?