はじめに
この記事はSwiftのカレンダーライブラリであるFSCalendarを使ってGoogleCalendarのようなUIを実現する方法について解説しています。
環境
- Xcode15.0.1
- Swift5.9
完成図
Google Calendarを意識しました。
開発の手順
- FSCalendarの準備
- カスタムのカレンダーセルを作成
- TableViewセルを作成
- 予定の反映
1. FSCalendarの準備
FSCalendarのインポート、およびStoryboardの準備をします。(詳細は省略します。)
2. カスタムのカレンダーセルを作成
デフォルトで登録されているセルを使用せず、自分でカレンダーのセルを作成します。
FSCalendarCellというクラスがあるはずです。今回はStoryboardで作成したいので、Also create XIB file
も忘れずにチェックします!
作成されたセルにLabelとTableViewを配置しました。今回は複数の予定をTableViewで表示します。
セルのサイズは50*75くらいがうまくおさまるというところでしょうか。後にオートレイアウトで調整します。
3. TableViewセルを作成
今回はGoogle Calendar風のUIにしたので、このように作りました。先ほどCalendar Cellの高さを75に設定したので、TableView Cellの高さは20にしておきました。2つほど予定が表示できる計算です。
コードはパーツを宣言して関連付けしておけばOKです。
import UIKit
class CalendarEventTableViewCell: UITableViewCell {
@IBOutlet var eventLabel: UILabel!
@IBOutlet var eventImageView: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
4. Calendar Cellの内容
Calendar Cellは難しくありません!CollectionViewCellやTableViewCellの内容を書くのと同じ容量で書けます。
import UIKit
import FSCalendar
class CustomCalendarCell: FSCalendarCell, UITableViewDataSource, UITableViewDelegate {
@IBOutlet var dateLabel: UILabel!
@IBOutlet var eventTableView: UITableView!
var eventList: [TaskObject]! //日付ごとの予定を格納する配列を作成
override func awakeFromNib() {
super.awakeFromNib()
self.titleLabel.isHidden = true //Calendar Cellのデフォルトの日付を消す
self.loadTableView()
}
//TableViewの初期設定を関数にまとめました。
func loadTableView() {
self.eventTableView.delegate = self
self.eventTableView.dataSource = self
self.eventTableView.register(UINib(nibName: "CalendarEventTableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
self.eventTableView.rowHeight = 20
self.eventTableView.allowsSelection = false //TableViewセルを選択不可にする
self.eventTableView.separatorStyle = .none
self.eventTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.eventList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! CalendarEventTableViewCell
cell.eventLabel.text = eventList[indexPath.row].taskname
return cell
}
}
ちょっとしたポイントとして、self.eventTableView.allowsSelection = false でTableViewCellを選択不可にすることがあります。
セルを選択できてしまうと、日付を選択しようとした時にTableViewのセルが反応してしまい鬱陶しいので...
TableViewに表示する内容はvar eventList: [TaskObject]!
という配列でFSCalendarを置いているViewControlllerから受け取ります。今回は省略しますが、日々の予定をTaskObjectというStructで作りました。Structについて詳しくはこちらから。
struct TaskObject{
let taskname:String
let deadline:Date
let uid:String
let finished:Bool
}
5. CalendarViewControllerのコードを書く
基本的なFSCalendarの設定は終わっている前提で、カスタムの CalendarCellを登録するにあたって特筆すべき点だけ書きます!
override func viewDidLoad() {
super.viewDidLoad()
setCalendar()
}
func setCalendar(){
self.calendar.dataSource = self
self.calendar.delegate = self
self.calendar.collectionView.register(UINib(nibName: "CustomCalendarCell", bundle: nil),forCellWithReuseIdentifier: "cell") //Calendar カスタムセルを登録する
calendar.rowHeight = 75
calendar.headerHeight = 20
calendar.appearance.selectionColor = .clear //選択した日付の色が変わるのを防ぐ(透明にする)
calendar.reloadData()
}
基本的にカスタムセルを登録する方法はTableViewやCollectionViewと同じです!
FSCalendarの日付選択を無効化することはできなさそうなので、calendar.appearance.selectionColor = .clear
で選択した際の色を透明にして見かけ上選択していないようにしています。
また、同様にセルの選択も瞬時に解除されるように以下コードを加えました。
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
calendar.deselect(date)
}
最後にCalendarCellの中身を書きます。
func calendar(_ calendar: FSCalendar, cellFor date: Date, at position: FSCalendarMonthPosition) -> FSCalendarCell {
let cell = calendar.dequeueReusableCell(withIdentifier: "cell", for: date, at: position) as! CustomCalendarCell
cell.dateLabel.text = String(getDay(date).2)
let tasksForDate = task.filter { task in
Calendar.current.isDate(task.deadline, inSameDayAs: date)
}
cell.eventList = tasksForDate
return cell
}
各セルの日付と、その日付に紐づく予定をCalendarCellに渡しています。TableViewCellやCollectionViewCellと同じですね!
cell.dateLabel.text = String(getDay(date).2)
で、各セルの日付を取得し自分で作った日付のラベルに表示しています。
参考までに、getDayはこのように実装しています。
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)
}
次に、読み込んだ予定を日付でフィルタリングします。
let tasksForDate = task.filter { task in
Calendar.current.isDate(task.deadline, inSameDayAs: date)
}
cell.eventList = tasksForDate
絞り込んだ予定をCalendarCellに送り、CalendarCellからさらにTableViewCellに送られて表示されるというわけです!
まとめ
意外と簡単でしたね!CustomCellの使い方に慣れていればできそうです。