FSCalendarのカレンダーにイメージ画像を表示する

#はじめに
カレンダー付きToDoアプリを制作する際にFSCalendarというライブラリを使用しましたので備忘録として投稿します。
初学者ですので訂正点ございましたら、ご指摘よろしくお願いします。


#概要
FSCalendarではカレンダーの日付の下に任意の条件でイメージ画像を表示することができます。
制作したアプリの用途に絡めると「ToDoの登録のステータスが完了済である日付にイメージ画像を表示」になります。

今回はRealmSwiftを組み合わせての実装になりますのでRealmSwiftに関しましては[こちら](https://realm.io/jp/docs/swift/latest/)参照ください。
また、FSCalendarの導入に関しましては[こちら](https://github.com/WenchaoD/FSCalendar)を参照ください。


#実行環境
【Xcode】 Version 11.7
【Swift】 version 5.2.4
【CocoaPods】version 1.9.3
【RealmSwift】 version 5.3.2
【FSCalendar】version 2.8.1


#実装後の画面
![スクリーンショット 2020-09-09 18.41.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692635/8d11279b-1e41-10e0-e087-303e73499bea.png)


#実装コード
全体のコードになります。
サンプルコードではなく、自作アプリのコードになりますので関連箇所を抜粋しております。
また、FSCalendarのDelegateとDataSourceはstoryboard上で追加しております。

```swift:Todo.swift
import Foundation
import RealmSwift

class Todo: Object {
    # ・・・省略・・・
    @objc dynamic var status: Bool = false
    # ・・・省略・・・
}
```

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

class MainViewController: UIViewController {

    @IBOutlet weak var calendar: FSCalendar!
    # ・・・省略・・・
    var datesWithStatus: Set<Bool> = []
    var checkWithStatus: Set<Bool> = [true]
    # ・・・省略・・・

    override func viewDidLoad() {
        super.viewDidLoad()
        # ・・・省略・・・
    }
}

// UIImageのリサイズ
extension UIImage {
    func resize(size _size: CGSize) -> UIImage? {
        let widthRatio = _size.width / size.width
        let heightRatio = _size.height / size.height
        let ratio = widthRatio < heightRatio ? widthRatio : heightRatio
        
        let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio)
        
        UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) // 変更
        draw(in: CGRect(origin: .zero, size: resizedSize))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return resizedImage
    }
}

extension MainViewController: FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance {
    // イメージ画像をつける
    func calendar(_ calendar: FSCalendar, imageFor date: Date) -> UIImage? {
        
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy/MM/dd"
        formatter.calendar = Calendar(identifier: .gregorian)
        formatter.timeZone = TimeZone.current
        formatter.locale = Locale.current
        let calendarDay = formatter.string(from: date)
        
        let perfect = UIImage(named: "perfect")
        let Resize:CGSize = CGSize.init(width: 18, height: 30) // サイズ指定
        let perfectResize = perfect?.resize(size: Resize)
        
        // Realmオブジェクトの生成
        let realm = try! Realm()
        // 参照（全データを取得）
        let todos = realm.objects(Todo.self).filter("dateString == '\(calendarDay)'")
        
        if todos.count > 0 {
            for i in 0..<todos.count {
                if i == 0 {
                    datesWithStatus = [todos[i].status]
                } else {
                    datesWithStatus.insert(todos[i].status)
                }
            }
        } else {
            datesWithStatus = []
        }
        if datesWithStatus == checkWithStatus {
            return perfectResize
        }
        return nil
    }
}
```


#実装方法
以下のメソッドを用いることでイメージ画像がつけられるようになります。

```swift:
func calendar(_ calendar: FSCalendar, imageFor date: Date) -> UIImage? {
    return image 
}
```

画面に表示されている月の日付（Date型）がループで投げられます。自動的にFor-in構文のようにdateの数だけこのメソッドが呼ばれるイメージです。
しかしこのまま用いても全ての日付の下にイメージ画像が付くようになるだけになります。
これをRealmSwiftと組み合わせることで、ToDoの登録のステータスが完了済である日付にイメージ画像を表示するようにしました。

####１．Todoのステータスを判別するデータ（status）を用意し、RealmSwiftに登録
- RealmSwiftに登録するTodoクラスに下記変数を用意します。

```swift:
@objc dynamic var status: Bool = false
```

- Realm Studioで確認すると赤枠の箇所になります。
![スクリーンショット 2020-09-09 19.43.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/692635/f4a738b0-f5e8-6765-f5d2-c351cf872443.png)

####２．RealmSwiftからデータを取得
- メソッドで呼ばれる日付（Date型）をRealmSwiftに保存している「dateString」と同じフォーマットに変換します。「dateString」はメソッドにより呼ばれた日付と照合させるために用意しました。詳しくは[こちら](https://qiita.com/0xTJtrFMRTbrbho/items/e57aada8a0b930d20452)参照ください。

```swift:
formatter.dateFormat = "yyyy/MM/dd"
let calendarDay = formatter.string(from: date)
```

- 変数「calendarDay」でフィルターをかけてRealmSwiftからデータ取得します。今回の用途は日付ごとに判別する必要があるため、filterメソッドで検索条件を指定します。

```swift:
// Realmオブジェクトの生成
let realm = try! Realm()
// 参照（全データを取得）
let todos = realm.objects(Todo.self).filter("dateString == '\(calendarDay)'")
```
####3．UIImageをリサイズ
- 元の画像サイズだと大きかったので、リサイズしてカレンダーの日付の下に表示できるサイズに変更しました。

```swift:
// UIImageのリサイズ
extension UIImage {
    func resize(size _size: CGSize) -> UIImage? {
        let widthRatio = _size.width / size.width
        let heightRatio = _size.height / size.height
        let ratio = widthRatio < heightRatio ? widthRatio : heightRatio
        
        let resizedSize = CGSize(width: size.width * ratio, height: size.height * ratio)
        
        UIGraphicsBeginImageContextWithOptions(resizedSize, false, 0.0) // 変更
        draw(in: CGRect(origin: .zero, size: resizedSize))
        let resizedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return resizedImage
    }
}
```

####4．取得したデータの中から必要なデータ（status）をピックアップして変数に格納
- RealmSwiftに登録データがある場合は、変数「datesWithStatus」に「status」の内容を格納。登録データがない場合は、変数「datesWithStatus」には何も入れません。

```swift:
if todos.count > 0 {
    for i in 0..<todos.count {
        if i == 0 {
            datesWithStatus = [todos[i].status]
        } else {
            datesWithStatus.insert(todos[i].status)
        }
    }
} else {
    datesWithStatus = []
}
```
- 変数「datesWithStatus」は配列のSetクラス（集合型）になります。Arrayクラスと異なる箇所としましてはインデックス番号が存在せず、重複が許されない型ということ違いがあります。今回RealmSwiftから取得するデータがtrueかfalseの重複する必要がなかったためSetクラス（集合型）にしております。詳細は[こちら](https://mayugezio.com/started/set/)参照ください。

```swift:
var datesWithStatus: Set<Bool> = []
```

####5．「true」のみの場合、画像（UIImage）を返す
- 変数「datesWithStatus」がtrueのみの場合は画像（UIImage）を返し、そうでなければnilを返す。

```swift:
if datesWithStatus == checkWithStatus {            
    return perfectResize
}
return nil
```

- 変数「datesWithStatus」と比較する変数「checkWithStatus」の型は合わせましょう。

```swift:
var checkWithStatus: Set<Bool> = [true]
```


#参考
[UIImageのリサイズ方法と注意点](https://qiita.com/ruwatana/items/473c1fb6fc889215fca3)