Realmを使用した日記アプリ

# Realmとは、、、
一言で言うとデーターベース。
iOSアプリでデータを保存したい時に使用されます。
データ保存は、Realmの他にUserDefaultsやCoreDataが用いられる事もあります。

CoreDataよりも直感的に利用でき、動作も速いことが特徴。
Swiftに対応したRealmSwiftがあるため、今回は使用していきます。。

以下、ドキュメント
[Realm](https://realm.io/jp/)

# アプリ作成
## 環境
- Xcode 10.1
- Swift 4.2
- Cocoapods 1.5.3

## ソースコード
gitHubにあげているので、全体のコードはこちら。
[shun6934/DiaryApp](https://github.com/shxun6934/DiaryApp)

## 参考サイト
- [Swiftで日記アプリを作ろう](https://qiita.com/ryoegu/items/1e5d1b8cadbf0061f6a7)
- [JBDataPicker](https://github.com/Tuslareb/JBDatePicker)
- [Cocoapods](https://cocoapods.org/)

## Realmの導入
### 1. Cocoapodsの導入
今回は、CocoapodsでRealmを管理します。
Cocoapodsは、Rubyが必須ですが、
Macではデフォで入っているため気にしなくていいです。

以下のコマンドを叩いて、インストールします。
`$ sudo gem install cocoapods`

正しくインストールされていれば、
`$ pod --version`
でversionを確認できます。

### 2. プロジェクト作成
Xcodeを開き、
`Create a new Xcode project -> Single View App`
で作成。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/b2f13c1f-148a-fcaa-ba64-50a661caa02c.png)

アプリ名は、`Diary_App`としておきます。

### 3. Podfile
ターミナルを開き、先ほど作成したアプリのディレクトリまで移動した後、
`$ pod init`
でPodfileを作成し、
`$ vi Podfile`
で編集します。

今回は、データを保存したいため、データベースであるRealmをSwiftで使うための`RealmSwift`と
カレンダーを表示するための`JBDatePicker`をライブラリーとして用います。

```ruby:Podfile
# Uncomment the next line to define a global platform for your project
# platform :ios, '12.1'
target 'Diary_App' do
   # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
   use_frameworks!
   # 以下、追加
   pod 'JBDatePicker'
   pod 'RealmSwift'

   # Pods for Diary_App
end
```
編集できたら保存し、
`$ pod install`
を行ないます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/a4efbd02-f84c-aae9-300e-b6be2b43cc38.png)

警告は出ていますが、無事にインストールされています。

これで準備できたので、これからコードを書いていきます。

## カレンダー表示
### 1. Viewの配置
`Main.storyboard`を開き、
カレンダーを表示したいViewControllerに`View`を配置します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/808812ad-7f5a-b111-11ec-abb0ccb170d8.png)

次に、先ほど配置したViewに対して`JBDatePickerView`Classを定義します。
Moduleに関しては、Classを入力した際に決まるため、入力する必要はありません。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/999c78ac-4890-3f15-dc4d-b3ad10cbfed9.png)

### 2. コードの紐付け
Storyboardに配置したViewをコードに紐付けさせる必要があります。
Viewを選択した状態で、`Ctr`を押しながらコード上に引っ張っていきます。
変数名は、`datePickerView`とします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/915d7c4c-d689-09ca-9c69-721347abfd0d.png)

これだけではエラーが出るため、モジュールをimportする必要があります。
また、Delegateプロトコルを宣言する必要があります。

```swift:ViewController.swift
import UIKit
import JBDatePicker // import

class ViewController: UIViewController, JBDatePickerViewDelegate { // Delegete宣言
    
    @IBOutlet weak var datePickerView: JBDatePickerView!  // カレンダー
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        datePickerView.delegate = self // DelegateをControllerに追加
        
    }

    // 日付選択時
    func didSelectDay(_ dayView: JBDatePickerDayView) {
        print("day selected:\(dayView.date!)")
    }!
    
}
```
ここで一旦実行してみましょう。
しっかりとカレンダーが表示されていたらokです。

また、任意の日付をタップすると
コンソールに日付が表示されると思います。

<img width="375" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/179470/07276e1d-b928-d43b-af35-1dabd310108c.png">

### 3. 日付の表示
コンソールに表示された日付は、タイムゾーンを無視しているため、
それを考慮する必要があります。

以下のコードを追加して、日本の日付にします。

```swift
    lazy var dateFormatter: DateFormatter = {
        var formatter = DateFormatter()
        formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM",
                                                        options: 0,
                                                        locale: Locale(identifier: "ja_JP"))
        formatter.timeZone = TimeZone(identifier: "Asia/Tokyo")
        return formatter
    }()
```

日付を変更できたため、`didSelectDay`を書き換えます。

```swift
    func didSelectDay(_ dayView: JBDatePickerDayView) {
        print("day selected:\(dateFormatter.string(from: dayView.date!))")
    }
```

もう一度実行すると、コンソールはこのように変化します。


```day selected:2018年12月5日```

## データベースの構築
### 1. モデルの用意
`File -> New -> File`
で新規ファイルを作成。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/6979ab30-e063-0f22-888f-1e0aee17e6d6.png)


`CocoaTouch`を選択し、名前を`Diary`、SubClassを`Object`にしたら、
保存します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/2e07cdac-f5e8-26c2-a3e1-fb56728b77a2.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/a29c9b02-f430-0d39-312b-8659167b4eea.png)

作成した後は、Objectでエラーが出るため、
`RealmSwift`をimportします。

```swift:Diary.swif
import RealmSwift

class Diary: Object {
    
}
```

### 2. モデルの構成
日記に必要な情報は、「日付」と「本文」です。
日付と本文は、`String型`です。
Primary Keyとして`date`を設定します。

```swift:Diary.swift
   @objc dynamic var date: String = ""
   @objc dynamic var context: String = ""

   open var primaryKey: String {
        return "date"
   }
```
データーベースのモデルができたため、日記を打ち込む画面を作成します。

## 日記
### 1. 日記画面
`Main.storyboard`で以下のように画面を作成します。

新しい画面の追加に伴い、ファイルも作成しておきます。
カレンダー表示と同様に、LabelやTextViewの紐付けをします。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/19edeef5-22c3-7509-4818-3000f5eb69e3.png)

```swift:ViewController.swift
import UIKit
import JBDatePicker

class ViewController: UIViewController, JBDatePickerViewDelegate {

    @IBOutlet weak var datePickerView: JBDatePickerView! 
    @IBOutlet weak var writeButton: UIButton! // 追加
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        datePickerView.delegate = self
        
    }
    
    func didSelectDay(_ dayView: JBDatePickerDayView) {
        print("day selected:\(dayView.date!)")
    }

    // 追加
    @IBAction func writeButtonPushed(_ sender: UIButton) {

    }
    
}

```

```swift:DiaryViewController.swift
import UIKit
import RealmSwift //ここでRealmを使う 

class DiaryViewController: UIViewController, UITextViewDelegate { //Delegate宣言
    
    // 紐付け
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var contextView: UITextView!
     
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // TextViewの設定
        contextView.delegate = self
        contextView.layer.borderColor = UIColor.black.cgColor
        contextView.layer.borderWidth = 1.0
        contextView.layer.cornerRadius = 10.0
        contextView.layer.masksToBounds = true
        
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // 「保存」ボタンを押した時の処理
    @IBAction func saveButtonPushed(_ sender: UIButton) {
        
    }
}
```

### 2. データの受け渡し
日記画面の日付に、カレンダー画面での日付を渡さなくてはいけないため、
`date`として渡してあげます。
まず、受け渡し元の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
        }
    }
```

`segue`を設定したため、Storyboardでも設定します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179470/ae63601f-f4f6-e090-08ef-cc57dee2d763.png)

次に、受け渡し先であるDiaryViewControllerに受け皿を用意してあげます。
困惑しないように、変数名を統一します。

```swift:DiaryViewController.swift
   var date: String! // 受け皿の用意


   override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        dateLabel.text = date // Labelに日付を表示

    }
```

これで日付の受け渡しができたため、
本文に書いた内容を保存していきます。

### 3. Realmに保存
保存したいタイミングが「保存」ボタンが押されたときであるため、
先ほど紐付けたメソッドに書いていきます。

```swift:DiaryViewController.swift

    // 「保存」ボタンを押した時の処理
    @IBAction func saveButtonPushed(_ sender: UIButton) {
        let realm = try! Realm() // Realmの初期化
        
        let diary = Diary() // モデルのインスタンス化
        diary.date = date
        diary.context = contextView.text
        
        try! realm.write {
            realm.add(diary, update: true) // Realmに追加
        }
        
        self.dismiss(animated: true, completion: nil) // 前の画面に戻る
    }
```

値がきちんと保存されているか確認するために、
Realm Browserで確認します。

確認方法は、以下のサイト参考。
[【Swift】Realm BrowserでRealm Mobile Databaseの中身を確認する](https://qiita.com/ryoegu/items/3a7543a954673396d10d)

### 4. 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.contextView.text = context
                }
            }
        }
        
    }
```

# 完成
これでできたため、実行してみましょう。

以下になったら、完成です。

<img width="375" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/179470/e15084cf-2c69-e7d5-1480-c68f6ff0c687.png">

<img width="375" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/179470/b80d728e-cd1f-63eb-952d-87ba1fd5ea9c.png">

### 更新

この記事で一つ欠点があったので、修正します。
コメントでのご指摘ありがとうございます。

カレンダー表示時に、日付をタップせずにそのまま書き込むボタンを押してしまうと
アプリが落ちてしまいます。

原因は、date