Swiftで日記アプリを作ろう 〜その3 Realm活用編〜

[前回の記事はこちら](http://qiita.com/ryoegu/items/1839d4eaecf74b49a745)

いよいよRealm活用編！
値を保存する方法をマスターしましょう！

[Realmの導入がまだの方はこちら](http://qiita.com/ryoegu/items/1839d4eaecf74b49a745)から。

# 1. モデルファイルを作成

Realmデータベースを使うために、まずはモデルを作成しましょう。
新規ファイルを追加するときにいつも行っている「New File...」を選択します。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/ac2af384-fc72-1146-a7a6-fdcf06cf909b.png)

Realm Pluginが正しく導入されていれば、下にスクロールすると「Realm Model Object」が表示されています。選択して次へ。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/694fa35a-24b4-013c-2a76-5174c646ee9b.png)

Model Object Classには、「Diary」と入力。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/1576e35d-b742-8aba-ca60-694f9b53cb1e.png)

最後に、「Create」ボタンをクリックして完了です。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/403558d9-12ee-6595-d7fd-fbf4f94c2e8c.png)

# 2. モデルファイルの中身を考える

いま、Objectクラスを継承した「Diary」クラスを作成しました。今度はこの中身を考えていきましょう。

いま、データベースの枠を作っているものと思ってください。
日記アプリにはどの要素が必要でしょうか？

今回は、以下のようなデータベースを作成することを目標にしていきます。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/2fda3c90-45d0-cadf-6fde-6669ac8f650e.png)

※どうでもいいですが、筆者の実話です。

そこで、今回は一番上の部分を考えていきます。
もし要素がほかにもあるようなら、自分で追加してかんがえてみてください。

このデータベースの中身ではなく枠の部分（今回は青い背景の部分）を「エンティティ」といいます。
データベース設計を考えるときにはエンティティの設計がとても大事です。

今回は「日付」「本文」「写真」と3つのエンティティがありますが、それぞれのエンティティに名前と型をつけていきましょう。

![image](https://qiita-image-store.s3.amazonaws.com/0/44105/037b1d23-04d3-cf25-72e3-ad69002089b5.png)



それでは、上図のとおりに`Diary.swift`にモデルを定義していきます。
上図のような表はデータベースを設計するときはかならず作りましょう！
Google Spreadsheetや、Excelのような表で構いません。あとで自分がコードを見たときに思い出しやすくなります。

```swift:Diary.swift
//
//  Diary.swift
//  DiarySampler
//
//  Created by Ryo Eguchi on 2017/01/02.
//  Copyright © 2017年 Ryo Eguchi. All rights reserved.
//

import Foundation
import RealmSwift

class Diary: Object {
    dynamic var date = ""
    dynamic var context = ""
    dynamic var photo: NSData? = nil

    override static func primaryKey() -> String? {
        return "date"
    }
    
}

```

`date`をPrimary Keyとして設定しました。Primary Key(以下、「PK」)として設定することで、一意性を保ちます。（重複してはいけないという意。）
※画像はその4で扱います。

それぞれのエンティティの作成方法は、[チートシート](https://realm.io/docs/swift/latest/#cheatsheet)を参考にしてください。

# 3. 日記作成画面の設置
Storyboardで、日記作成画面を作りましょう。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/5c6beb33-f782-39bc-d456-10831e664b6d.png)


関連付けもわすれずにしておきましょう。



```swift:ViewController.swift
    @IBOutlet var writeButton: UIButton!

// （中略）

    @IBAction func writeButtonPushed(_ sender: UIButton) {
        
        
    }
```

続いて、2番目のViewController（DiaryViewController）についても同様に関連付けします。
あらかじめNew File...で新しくViewControllerファイルを作っておきましょう。

```swift:DiaryViewController.swift
//
//  DiaryViewController.swift
//  DiarySampler
//
//  Created by Ryo Eguchi on 2017/01/02.
//  Copyright © 2017年 Ryo Eguchi. All rights reserved.
//

import UIKit

//このファイル内でRealmを使うのでここを追加
import RealmSwift

class DiaryViewController: UIViewController {

    @IBOutlet var dateLabel: UILabel!
    
    @IBOutlet var contextTextView: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func saveButtonPushed(_ sender: UIButton) {
    }

}

```

# 4. 情報をDiaryViewControllerに集約させよう
今回のアプリの構造は次のとおりです。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/8aebfaf2-92b6-12dd-cee5-8b278f1432f7.png)



つまり、すべての情報が「DiaryViewController」に集約され、そこから情報がRealm Databaseに格納されます。
そこで、情報をすべてDiaryViewControllerに集約させましょう。


## 4-1. dateの受け皿を準備する
DiaryViewControllerに、dateの受け皿を準備しましょう。
変数を宣言します。

```swift:DiaryViewController.swift
    var date: String!
```

実際にラベルに表示させるようにしておきましょう。

```swift:DiaryViewController.swift
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        dateLabel.text = date

    }
```

## 4-2. 受け渡し元のViewControllerで処理を記述

```swift:ViewController.swift
    var date: String!

    // （中略）

    func didSelectDay(_ dayView: JBDatePickerDayView) {
        print("date selected: \(dateFormatter.string(from: dayView.date!))")
        date = dateFormatter.string(from: dayView.date!) //追加
        
    }
    
    @IBAction func writeButtonPushed(_ sender: UIButton) {
        self.performSegue(withIdentifier: "toDiary", sender: nil)
    }
    
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if (segue.identifier == "toDiary") {
            let diaryView = segue.destination as! DiaryViewController
            diaryView.date = self.date
        }
    }
```

まずはじめに、dateをString型で宣言しておきます。

1つめのメソッド「didSelectDay」は、JBDatePickerDayViewのDelegateメソッドです。
dateにstring型で日付を挿入しています。

2つめのメソッド「writeButtonPushed」は、「書く」ボタンをおした時のメソッドで、画面遷移のコードを書いています。

3つめのメソッド「prepare for segue」は、画面遷移するときに**必ず呼ばれるメソッド**です。（知らなかったら覚えておこう！）
今回は、「toDiary」の画面遷移をするときに、遷移先のdateという変数に遷移元の変数dateの値を代入します。これで、画面遷移したときに遷移先のdate変数に初期値がセットされました。

## 4-3. Storyboardで画面をつなげる
Storyboardで1つめの画面と2つめの画面をつなげておきましょう。
Identifierは「toDiary」と記述します。
![image](https://qiita-image-store.s3.amazonaws.com/0/44105/ebc336a7-47bf-c893-c822-383066fee04b.png)

これで、保存はされませんが、シミュレータで実行して画面遷移ができることを確認しましょう。

![image](https://qiita-image-store.s3.amazonaws.com/0/44105/5b17924c-e982-ea86-c3eb-d5e6b06aca1f.png)

ただしく実行できれば、上図のように画面上部のラベルに日付が表示されます。

# 5. Realmに保存

最後に、Realmに保存（書き込み）しましょう。

```swift:DiaryViewController.swift
    @IBAction func saveButtonPushed(_ sender: UIButton) {
        
        // STEP.1 Realmを初期化
        let realm = try! Realm()
        
        //STEP.2 保存する要素を書く
        let diary = Diary()
        diary.date = date
        diary.context = contextTextView.text
        
        //STEP.3 Realmに書き込み
        try! realm.write {
            realm.add(diary, update: true)
        }
        
        
        //画面遷移して前の画面に戻る
        self.dismiss(animated: true, completion: nil)
        
    }
```


この地点でRealmに値を書き込んでいるので、きちんと値が書き込まれているかRealm Browserで確認してみましょう。
確認方法はこちらの記事が参考になります。
[【Swift】Realm BrowserでRealm Mobile Databaseの中身を確認する](http://qiita.com/ryoegu/items/3a7543a954673396d10d)

# 6. Realmから値を読み込む

もし、すでにRealmに値がある場合は、Realmから本文を取ってくるようにしましょう。

```swift:DiaryViewController.swift
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        dateLabel.text = date
        
        DispatchQueue(label: "background").async {
            let realm = try! Realm()

            if let savedDiary = realm.objects(Diary.self).filter("date == '\(self.date!)'").last {
                let context = savedDiary.context
                DispatchQueue.main.async {
                    self.contextTextView.text = context
                }
            }
        }
        
    }
```

`DispatchQueue(label:"background")`では、メインスレッドではなくバックスレッドで処理を実行しています。これをすることで、メインスレッドの処理に影響することなくバックスレッドでQueue（検索して読み込み）を行うことができます。

```swift
let savedDiary = realm.objects(Diary.self).filter("date == '\(self.date!)'").last
```

これは、

```
realm.objects(Diary.self)
```

ここで、Diaryオブジェクトをすべて読みこんでいます。そこに、

```swift
.filter("date == '\(self.date!)'")
```

filter（フィルタ）をかけてあげます。今回は、dateエンティティが「self.date!」（選択した日付）と等しいものをすべて読み込んでいます。

さらに、

```swift
.last
```

これで、フィルタをかけたオブジェクトのうちで一番最後（最新）のもの、という意味です。

```swift
DispatchQueue.main.async {
   self.contextTextView.text = context
}
```
これは、DispatchQueueで処理するスレッドを変えていますが、画面表示に関わることなのでバックスレッドでは処理できません。そこで、あえてメインスレッドに変えて処理を実行しています。