LoginSignup
3
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-09-10

はじめに

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

概要

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

今回はRealmSwiftを組み合わせての実装になりますのでRealmSwiftに関しましてはこちら参照ください。
また、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

実装コード

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

Todo.swift
import Foundation
import RealmSwift

class Todo: Object {
    # ・・・省略・・・
    @objc dynamic var status: Bool = false
    # ・・・省略・・・
}
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
    }
}

実装方法

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

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

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

1.Todoのステータスを判別するデータ(status)を用意し、RealmSwiftに登録

  • RealmSwiftに登録するTodoクラスに下記変数を用意します。
@objc dynamic var status: Bool = false
  • Realm Studioで確認すると赤枠の箇所になります。 スクリーンショット 2020-09-09 19.43.41.png

2.RealmSwiftからデータを取得

  • メソッドで呼ばれる日付(Date型)をRealmSwiftに保存している「dateString」と同じフォーマットに変換します。「dateString」はメソッドにより呼ばれた日付と照合させるために用意しました。詳しくはこちら参照ください。
formatter.dateFormat = "yyyy/MM/dd"
let calendarDay = formatter.string(from: date)
  • 変数「calendarDay」でフィルターをかけてRealmSwiftからデータ取得します。今回の用途は日付ごとに判別する必要があるため、filterメソッドで検索条件を指定します。
// Realmオブジェクトの生成
let realm = try! Realm()
// 参照(全データを取得)
let todos = realm.objects(Todo.self).filter("dateString == '\(calendarDay)'")

3.UIImageをリサイズ

  • 元の画像サイズだと大きかったので、リサイズしてカレンダーの日付の下に表示できるサイズに変更しました。
// 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」には何も入れません。
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クラス(集合型)にしております。詳細はこちら参照ください。
var datesWithStatus: Set<Bool> = []

5.「true」のみの場合、画像(UIImage)を返す

  • 変数「datesWithStatus」がtrueのみの場合は画像(UIImage)を返し、そうでなければnilを返す。
if datesWithStatus == checkWithStatus {            
    return perfectResize
}
return nil
  • 変数「datesWithStatus」と比較する変数「checkWithStatus」の型は合わせましょう。
var checkWithStatus: Set<Bool> = [true]

参考

UIImageのリサイズ方法と注意点

3
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3