#はじめに
カレンダー付き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
#実装コード
全体のコードになります。
サンプルコードではなく、自作アプリのコードになりますので関連箇所を抜粋しております。
また、FSCalendarのDelegateとDataSourceはstoryboard上で追加しております。
import Foundation
import RealmSwift
class Todo: Object {
# ・・・省略・・・
@objc dynamic var status: Bool = false
# ・・・省略・・・
}
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
####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]