【Swift3】カレンダー型の日記アプリを作ってRealmで保存する

f:id:utr066:20180311144138j:plain

日記アプリを作る。 カレンダーの日付をタップして、その日の日付に対するメモを残す。メモはRealmに保存する。

カレンダー作成

https://qiita.com/ryoegu/items/1e5d1b8cadbf0061f6a7 この記事を大いに参考にしました。

cocoaPod インストール

cocoa podをインストールしていない人は、インストールしましょう。

$ sudo gem install cocoapods
$ pod setup

セットアップ完了。 完了したら、作ったプロジェクトのディレクトリでPodfileを作成します。

pod init

自分はDiaryという名前のプロジェクトで作ったので、Diary以下でファイルがあるか確認。Podfileがあればok。

Diary  Diary.xcworkspace  DiaryUITests  Podfile

作ったPodfileの中にJBDatePickerを入れます。

  1 # Uncomment the next line to define a global platform for your project
  2 # platform :ios, '9.0'
  3
  4 target 'Diary' do
  5   # Comment the next line if you're not using Swift and don't want to use dy    namic frameworks
  6   use_frameworks!
  7
  8   # Pods for Diary
  9       pod 'JBDatePicker'
            //↑を追加。
 10   target 'DiaryTests' do
 11     inherit! :search_paths
 12     # Pods for testing
 13   end
 14
 15   target 'DiaryUITests' do
 16     inherit! :search_paths
 17     # Pods for testing
 18   end
 19
 20 end
~

その後、Diaryディレクトリで以下コマンドを実行。

pod install

これで新たなファイルができるので、作ったファイルをxcodeで開く。 f:id:utr066:20171102211524j:plain

このファイルを開いて、buildしてsuccessするか試してみましょう。

カレンダー作成は以下の記事と同じ。 https://qiita.com/ShinokiRyosei/items/3090290cb72434852460

import UIKit
import JBDatePicker

class ViewController: UIViewController, JBDatePickerViewDelegate {


    @IBOutlet var datePicker: JBDatePickerView!

    lazy var dateFormatter: DateFormatter = {
        var formatter = DateFormatter()
        formatter.timeStyle = .none
        formatter.dateStyle = .medium
        return formatter
    }()


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        datePicker.delegate = self

    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        datePicker.updateLayout()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

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


}

日付タップ後に画面遷移→日記を書く

f:id:utr066:20171102211620j:plain

現在このような表示ですが、表示ができただけでタップしても何も起きません。タップしたら、日記を書くページに画面遷移してみます。

別のview controllerを作る

画面遷移するために、view controllerをもう一個おきます。 f:id:utr066:20171102211650j:plain f:id:utr066:20171102211702j:plain f:id:utr066:20171102211710j:plain f:id:utr066:20171102211747j:plain

作成したら、新しく作ったviewControllerのclassに新しいファイルを割り当てます。

f:id:utr066:20171102211801j:plain

画面遷移するために、別のストーリーボードにcontrolを押しながらくっつけておきましょう。

 画面遷移のコードを書く

日付をタップしたら次の画面にいきたいです。タップしたら呼ばれるメソッドがdidSelectDayなので、その中に画面遷移のためのメソッドを書きます。

    func didSelectDay(_ dayView: JBDatePickerDayView) {
        date = "\(dateFormatter.string(from: dayView.date!))"
        print("date selected: \(dateFormatter.string(from: dayView.date!))")

        self.performSegue(withIdentifier: "next", sender: nil)
        //画面遷移のためのメソッド
    }

withIdentifierの値が"next"になっている部分は、storyboardで設定します。 f:id:utr066:20171102211815j:plain

これで画面遷移はできる。

遷移した先では、送信ボタンをクリックしたら前の画面に戻るようにします。送信ボタンをactionで紐づけて元の画面に戻るコードを書く。

    @IBAction func save(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }

これで送信ボタンを押したら元の画面に戻ります。

画面遷移時に値を渡す

今回の場合、カレンダーの日付をクリックしたらその日付を一緒に送りたいです。

//viewController

    func didSelectDay(_ dayView: JBDatePickerDayView) {
        print("date selected: \(dateFormatter.string(from: dayView.date!))")
        self.performSegue(withIdentifier: "next", sender: nil)
    }

dateFormatter.string(from: dayView.date!で取れているので、これを画面遷移先に渡します。

遷移先に渡す際には、以下のメソッドを使います。 self.performSegue(withIdentifier: "next", sender: nil)が呼ばれたら以下のメソッドも呼ばれ、値を次の画面に渡すことができる。

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "next" {
            let secondViewController = segue.destination as! WriteViewController
            secondViewController.parameters = date
        }
    }

ただ、問題はdayViewという値がdidSelectDayメソッドの中でしか使えない点です。prepareメソッドでも使いたいので、グローバル変数にして使います。

var date = ""
//上の方に定義
func didSelectDay(_ dayView: JBDatePickerDayView) {
    date = "\(dateFormatter.string(from: dayView.date!))"
    //定義した変数にタップされた日付を入れる
    print("date selected: \(dateFormatter.string(from: dayView.date!))")
    self.performSegue(withIdentifier: "next", sender: nil)   
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == "next" {
        let writeViewController = segue.destination as! WriteViewController
        writeViewController.parameter = date
    }
}

prepareではdateをdateという変数に入れます。このdateはwriteViewControllerに定義しているものです。

import UIKit

class WriteViewController: UIViewController {
    
    var date = ""

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        print(parameter)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    @IBAction func save(_ sender: Any) {
        self.dismiss(animated: true, completion: nil)
    }

}

ここのdateに画面遷移時に値をセットしているんですね。

relamに書いた内容を登録する

書いた日記を登録するために、Realmを使ってみます。 PodfileにRealmSwiftを追加しましょう。

  8   # Pods for Diary
  9       pod 'JBDatePicker'
 10       pod 'RealmSwift'
pod install

これでRealmSwiftが入ったので、使っていけます。

書いた日記を司るクラスを作る。 f:id:utr066:20171102211848j:plain

NSObjectでDiaryという名前でファイルを作ります。

import UIKit
import RealmSwift

class Diary: Object {
}

作ったファイルにimport RealmSwiftを記述し、NSObjectをOBjectに変更。buildしてみましょう。 うまくいかなかった場合、cleanしてからbuildしましょう。 f:id:utr066:20171102211905j:plain

import UIKit
import RealmSwift

class Diary: Object {
    dynamic var text = ""
}

textという名前で、送信された文字を保存したいと思います。Realmを使用する際、プロパティにはdynamicをつけるルールみたいです。

f:id:utr066:20171102212014j:plain

textViewoutletで紐づけておきます。 紐づけたら、保存処理を行いましょう。

    @IBAction func save(_ sender: Any) {
        let realm = try! Realm()
        let diary = Diary()
        diary.text = textView.text!
        try! realm.write {
            realm.add(diary)
        }
        self.dismiss(animated: true, completion: nil)
    }

returnボタンを押した時にキーボードを閉じるようにdelegateも設定しておきます。

class WriteViewController: UIViewController, UITextViewDelegate 
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
     if (text == "\n") {
        //あなたのテキストフィールド
        textView.resignFirstResponder()
        return false
    }
    return true
}    

realmに保存した値を確認する。

Realm Browserをインストールします。 その後、以下の記事を参考に確認します。 https://qiita.com/ryoegu/items/3a7543a954673396d10d

    @IBAction func save(_ sender: Any) {
        
        let realm = try! Realm()
        print(Realm.Configuration.defaultConfiguration.fileURL!)
//ファイルパスを表示させる。
        let diary = Diary()
        diary.text = textView.text!
        try! realm.write {
            realm.add(diary)
        }
        self.dismiss(animated: true, completion: nil)
        
    }

ターミナルに出るfile://から始まる文章がパスを表しています。

file:///Users/〇〇〇〇default.realm

これの/Users/〇〇〇〇default.realmまでコピーします。

open /Users/〇〇〇〇default.realm

コピーしたらターミナルに上記を打ち込む。すると、Realm Browserが立ち上がります。 「Allow」をクリックして表示してみます。 f:id:utr066:20171102212034j:plain

ちゃんと入力した値が入っていました。

値が入っていたら日付をタップした際にその値を表示させたいです。 ViewControllerを編集します。

    override func viewDidLoad() {
        super.viewDidLoad()

        DispatchQueue(label: "background").async {
            let realm = try! Realm()
            
            if let savedDiary = realm.objects(Diary.self).filter("date == '\(self.date)'").last {
                let text = savedDiary.text
                DispatchQueue.main.async {
                    self.textView.text = text
                }
            }
        }
        
        textView.delegate = self
    }

これで、日付をタップした際にRealmにデータがあれば取ってきて表示してくれます。