Swift4 + Realmでカレンダーアプリを作ってみた。

# **目次**
- カレンダーアプリを作ろう！
- 使用したもの
- 事前準備
- Realmを使う
- FSCalendar&CalculateCalendarLogicを使う
- スケジュール登録機能を追加する
- 完成
- 感想
- 参考文献


# **カレンダーアプリを作ろう!**
今回はFSCalendar + Realmを使って、スケジュール登録ができるカレンダーアプリを作ってみました。
<img width="410" alt="スクリーンショット 2018-05-04 21.41.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/10703328-5adc-8574-f491-b37709b4783b.png">


# **使用したもの**
Xcode9.3
Swift4.1
CocoaPods
FSCalendar
CalculateCalendarLogic
Realm

**※尚、今回CocoaPodsの導入は割愛します。**


# **事前準備**
まず、CocoaPodsを使って、FSCalendarとCalculateCalendarLogicとRealmをインストールする。
ターミナルで、以下のコードを記入し実行。

```
pod init
```
これで、Podfileが生成される。
次に、Podfileを以下の様に編集する。

```
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'xxx' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!
  
  # 以下の行を追加
  pod 'RealmSwift'
  pod 'FSCalendar'
  pod 'CalculateCalendarLogic'
  # 追加ここまで
　
  # Pods for xxx

  target 'xxxTests' do
  # 以下略
```




そして、ターミナルで、

```
pod install
```

を実行する。
これで、インストール完了。

# **Realmを使う**
まず、RealmでDB設定を行う。

今回は、

- **日付(date)**
- **スケジュール(event)**

を登録する。

`Command + N`キーを押す->`iOS`->`Source`から`Swift File`を選択し、Swiftファイルを新規作成。

<img width="729" alt="スクリーンショット 2018-05-04 22.08.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/d4b97f32-c5ba-5003-2ba9-e2ae6faa3a46.png">

作成したSwiftファイルに、以下のコードを記述する。

```swift:Event.swift
import Foundation
import RealmSwift

class Event: Object {
    
    @objc dynamic var date: String = ""
    @objc dynamic var event: String = ""
    
}

```

これで、RealmのDB設定は完了。

# **FSCalendar&CalculateCalendarLogicを使う**
次に、FSCalendar&CalculateCalendarLogicを使って、カレンダー機能を実装する。
簡単に説明すると、

- **FSCalendar -> カレンダー機能を実装する**
- **CalculateCalendarLogic -> 土日・祝日の判定機能を実装する**


という感じだ。FSCalendarには、祝日を判別する機能はないので、そこをCalculateCalendarLogicで補っている。
`ViewController.swift`に以下のコードを記入。

```swift:ViewController.swift
import UIKit
import FSCalendar
import CalculateCalendarLogic
import RealmSwift

//ディスプレイサイズ取得
let w = UIScreen.main.bounds.size.width
let h = UIScreen.main.bounds.size.height

class ViewController: UIViewController, FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance {
    //スケジュール内容
    let labelDate = UILabel(frame: CGRect(x: 5, y: 580, width: 400, height: 50))
    //「主なスケジュール」の表示
    let labelTitle = UILabel(frame: CGRect(x: 0, y: 530, width: 180, height: 50))
    //カレンダー部分
    let dateView = FSCalendar(frame: CGRect(x: 0, y: 30, width: w, height: 400))
    //日付の表示
    let Date = UILabel(frame: CGRect(x: 5, y: 430, width: 200, height: 100))
    override func viewDidLoad() {
        super.viewDidLoad()
        //カレンダー設定
        self.dateView.dataSource = self
        self.dateView.delegate = self
        self.dateView.today = nil
        self.dateView.tintColor = .red
        self.view.backgroundColor = .white
        dateView.backgroundColor = .white
        view.addSubview(dateView)
        
        //日付表示設定
        Date.text = ""
        Date.font = UIFont.systemFont(ofSize: 60.0)
        Date.textColor = .black
        view.addSubview(Date)
        
        //「主なスケジュール」表示設定
        labelTitle.text = ""
        labelTitle.textAlignment = .center
        labelTitle.font = UIFont.systemFont(ofSize: 20.0)
        view.addSubview(labelTitle)
        
        //スケジュール内容表示設定
        labelDate.text = ""
        labelDate.font = UIFont.systemFont(ofSize: 18.0)
        view.addSubview(labelDate)
        
        //スケジュール追加ボタン
        let addBtn = UIButton(frame: CGRect(x: w - 70, y: h - 70, width: 60, height: 60))
        addBtn.setTitle("+", for: UIControlState())
        addBtn.setTitleColor(.white, for: UIControlState())
        addBtn.backgroundColor = .orange
        addBtn.layer.cornerRadius = 30.0
        addBtn.addTarget(self, action: #selector(onClick(_:)), for: .touchUpInside)
        view.addSubview(addBtn)
        
        
    }
    
    fileprivate let gregorian: Calendar = Calendar(identifier: .gregorian)
    fileprivate lazy var dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        return formatter
    }()
    
    // 祝日判定を行い結果を返すメソッド
    func judgeHoliday(_ date : Date) -> Bool {
        //祝日判定用のカレンダークラスのインスタンス
        let tmpCalendar = Calendar(identifier: .gregorian)
        
        // 祝日判定を行う日にちの年、月、日を取得
        let year = tmpCalendar.component(.year, from: date)
        let month = tmpCalendar.component(.month, from: date)
        let day = tmpCalendar.component(.day, from: date)
        
        let holiday = CalculateCalendarLogic()
        
        return holiday.judgeJapaneseHoliday(year: year, month: month, day: day)
    }
    
    // date型 -> 年月日をIntで取得
    func getDay(_ date:Date) -> (Int,Int,Int){
        let tmpCalendar = Calendar(identifier: .gregorian)
        let year = tmpCalendar.component(.year, from: date)
        let month = tmpCalendar.component(.month, from: date)
        let day = tmpCalendar.component(.day, from: date)
        return (year,month,day)
    }
    
    //曜日判定
    func getWeekIdx(_ date: Date) -> Int{
        let tmpCalendar = Calendar(identifier: .gregorian)
        return tmpCalendar.component(.weekday, from: date)
    }
    
    // 土日や祝日の日の文字色を変える
    func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, titleDefaultColorFor date: Date) -> UIColor? {
        //祝日判定をする
        if self.judgeHoliday(date){
            return UIColor.red
        }
        
        //土日の判定
        let weekday = self.getWeekIdx(date)
        if weekday == 1 {
            return UIColor.red
        }
        else if weekday == 7 {
            return UIColor.blue
        }
        
        return nil
    }
    
}
```



# **スケジュール登録機能を追加する**
まず、スケジュール登録ページ(`EventViewController.swift`)を追加する。
`Command + N`キーを押す->`iOS`->`Source`から`Cocoa Touch Class`を選択し、新規作成。

<img width="729" alt="スクリーンショット 2018-05-04 22.08.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/0c747cbb-c192-8548-547d-e316c3ad71e0.png">

そして、`Main.StoryBoard`に`View Controller`を追加し、
`Custom Class`の`class`を`EventViewController`にする。

そして、各StoryBoard IDを以下の様にする。

**ViewController -> Main**
**EventViewController -> Insert**

まずは、画面遷移を実装する。
`ViewController.swift`に以下のコードをclass内に追加する。

```swift:ViewController.swift
    //画面遷移(スケジュール登録ページ)
    @objc func onClick(_: UIButton) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let SecondController = storyboard.instantiateViewController(withIdentifier: "Insert")
        present(SecondController, animated: true, completion: nil)
    }
```

次に、スケジュール登録ページを実装する。
以下のコードを`EventViewController.swift`に記入。

```swift:EventViewController.swift
import UIKit
import RealmSwift
//ディスプレイサイズ取得
let w2 = UIScreen.main.bounds.size.width
let h2 = UIScreen.main.bounds.size.height
//スケジュール内容入力テキスト
let eventText = UITextView(frame: CGRect(x: (w2 - 300) / 2, y: 100, width: 300, height: 200))

//日付フォーム(UIDatePickerを使用)
let y = UIDatePicker(frame: CGRect(x: 0, y: 300, width: w2, height: 300))
//日付表示
let y_text = UILabel(frame: CGRect(x: (w2 - 300) / 2, y: 570, width: 300, height: 20))
class EventViewController: UIViewController {
    var date: String!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //スケジュール内容入力テキスト設定
        eventText.text = ""
        eventText.layer.borderColor = UIColor.gray.cgColor
        eventText.layer.borderWidth = 1.0
        eventText.layer.cornerRadius = 10.0
        view.addSubview(eventText)

        //日付フォーム設定
        y.datePickerMode = UIDatePickerMode.date
        y.timeZone = NSTimeZone.local
        y.addTarget(self, action: #selector(picker(_:)), for: .valueChanged)
        view.addSubview(y)
        
        //日付表示設定
        y_text.backgroundColor = .white
        y_text.textAlignment = .center
        view.addSubview(y_text)
        
        //「書く!」ボタン
        let eventInsert = UIButton(frame: CGRect(x: (w2 - 200) / 2, y: 600, width: 200, height: 50))
        eventInsert.setTitle("書く！", for: UIControlState())
        eventInsert.setTitleColor(.white, for: UIControlState())
        eventInsert.backgroundColor = .orange
        eventInsert.addTarget(self, action: #selector(saveEvent(_:)), for: .touchUpInside)
        view.addSubview(eventInsert)
        
        //「戻る!」ボタン
        let backBtn = UIButton(frame: CGRect(x: (w - 200) / 2, y: h - 50, width: 200, height: 30))
        backBtn.setTitle("戻る", for: UIControlState())
        backBtn.setTitleColor(.orange, for: UIControlState())
        backBtn.backgroundColor = .white
        backBtn.layer.cornerRadius = 10.0
        backBtn.layer.borderColor = UIColor.orange.cgColor
        backBtn.layer.borderWidth = 1.0
        backBtn.addTarget(self, action: #selector(onbackClick(_:)), for: .touchUpInside)
        view.addSubview(backBtn)
        
    }
    
    //画面遷移(カレンダーページ)
    @objc func onbackClick(_: UIButton) {
        dismiss(animated: true, completion: nil)
    }
    
    //日付フォーム
    @objc func picker(_ sender:UIDatePicker){
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd"
        y_text.text = formatter.string(from: sender.date)
        view.addSubview(y_text)
    }
    
    //DB書き込み処理
    @objc func saveEvent(_ : UIButton){
        print("データ書き込み開始")
            
            let realm = try! Realm()

            try! realm.write {
                //日付表示の内容とスケジュール入力の内容が書き込まれる。
                let Events = [Event(value: ["date": y_text.text, "event": eventText.text])]
                realm.add(Events)
                print("データ書き込み中")
            }

        print("データ書き込み完了")
        
        //前のページに戻る
        dismiss(animated: true, completion: nil)
        
    }
    
}

```

DBに書き込んだ値を取得し、カレンダーに反映させる。
`Viewcontroller.swift`に以下のコードを追加する。

```swift:ViewController.swift
//カレンダー処理(スケジュール表示処理)
    func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition){
        
        labelTitle.text = "主なスケジュール"
        labelTitle.backgroundColor = .orange
        view.addSubview(labelTitle)
        
        //予定がある場合、スケジュールをDBから取得・表示する。
        //無い場合、「スケジュールはありません」と表示。
        labelDate.text = "スケジュールはありません"
        labelDate.textColor = .lightGray
        view.addSubview(labelDate)
        
        let tmpDate = Calendar(identifier: .gregorian)
        let year = tmpDate.component(.year, from: date)
        let month = tmpDate.component(.month, from: date)
        let day = tmpDate.component(.day, from: date)
        let m = String(format: "%02d", month)
        let d = String(format: "%02d", day)
        
        let da = "\(year)/\(m)/\(d)"
        
        //クリックしたら、日付が表示される。
        Date.text = "\(m)/\(d)"
        view.addSubview(Date)
        
        //スケジュール取得
        let realm = try! Realm()
        var result = realm.objects(Event.self)
        result = result.filter("date = '\(da)'")
        print(result)
        for ev in result {
            if ev.date == da {
                labelDate.text = ev.event
                labelDate.textColor = .black
                view.addSubview(labelDate)
            }
        }
        
        
        
    }
```
問題なければ、ここで完成。

# **完成**

**・ 起動時**
<img width="412" alt="スクリーンショット 2018-05-04 21.40.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/204a0b70-b6db-1931-b22a-4c380162e77b.png">

**・ スケジュール登録ページ**
<img width="410" alt="スクリーンショット 2018-05-04 21.40.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/c16a7524-df78-caef-047f-f3c1adf1a503.png">

**・ 日付選択(スケジュールなし)**
<img width="412" alt="スクリーンショット 2018-05-04 21.41.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/c6f287a8-e85b-70f4-8890-7adef42d543e.png">

**・ 日付選択(スケジュールあり)**
<img width="410" alt="スクリーンショット 2018-05-04 21.41.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/154610/20e875dc-efc2-8c1f-4f62-49aaa27246b3.png">

# **感想**
思ってたより、大変でした。
スケジュール登録機能が凄く大変でした。
Realmが全然ダメダメだったので、勉強する必要がありますね。。。。

# **参考文献**
[Swiftで簡単にカレンダーを作ろう！(FSCalendar)](https://qiita.com/Koutya/items/f5c7c12ab1458b6addcd)
[Swiftで日記アプリを作ろう 〜その3 Realm活用編〜](https://qiita.com/ryoegu/items/2249555be43dc7ed617a)