Swift3 スワイプで切替えるカレンダーでCollectionViewCellの選択をうまく解除できない
下記の2ページを参考にスワイプで切替えるカレンダーを作成したのですが、CollectionViewCellの選択をうまく解除できません。
http://qiita.com/kitanoow/items/65b1418527eabf31e45b
http://qiita.com/sakuran/items/3c2c9f22cbcbf4aff731
実装したいこと
カレンダーの内容として以下のものを実装したいです。
- 左右にスワイプで月ごとのカレンダーを切替える
- 日付をタップしたセルの背景に予め用意した画像を表示
- 同月内の日付をタップしたら過去に選択したセルの画像表示を解除
- 他の月に移動して日付をタップしたら過去に選択した他月のセルの画像表示を解除
解決したい問題
上記の1から3まではなんとか動かせるようになったのですが、問題なのは4番の1画面進んで1画面戻った場合の下記のような挙動です。
こんなイメージで、スワイプしたら画面が左右にスクロールするものです。
[前の月] [現在の月] [次の月]
[現在の月]セルを選択
セルの背景画像を表示
↓
[次の月]に1画面移動
セルを選択→セルの背景画像を表示
↓
先程の[現在の月]に1画面戻る
選択したセルの背景が解除されていない
[現在の月]→[次の月]→[次の月]と2画面移動した後に戻る場合には選択が解除されています。
1画面進んで、1画面戻る場合には既に生成されたCollectionViewがうまく更新できていないからだとは思うのですが、どのようにすれば解決できるのか分かりません。
どうぞよろしくお願いいたします。
以下はソースコードです。
// AppDelegat.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var scheduleAt : Date?
var eventType = 1
var selCalDayID : Int?
}
// ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let calView = CalendarView(frame: CGRect(x: 0, y: 20, width: UIScreen.main.bounds.size.width, height: 290))
self.view.addSubview(calView)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
import UIKit
// CalendarView.swift
class CalendarView : UIView, UIScrollViewDelegate {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
var currentYear:Int = 0
var currentMonth:Int = 0
var currentDay:Int = 0
let daysPerWeek: Int = 7
var currentDate = Date()
var scrollView: UIScrollView!
var currentMonthView : CalCollectionView!
var nextMonthView : CalCollectionView!
var prevMonthView : CalCollectionView!
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var getNextMonth:Date!
var getPrevMonth:Date!
override init(frame:CGRect){
super.init(frame: frame)
let screenWidth : CGFloat = frame.size.width
let screenHeight : CGFloat = 290
// まずはscrollviewを追加
scrollView = UIScrollView(frame: self.bounds)
scrollView.backgroundColor = UIColor.white
scrollView.contentSize = CGSize(width: frame.size.width * 3.0,height: frame.size.height)
scrollView.contentOffset = CGPoint(x: screenWidth , y: 0.0)
scrollView.delegate = self
scrollView.isPagingEnabled = true
scrollView.showsHorizontalScrollIndicator = false
scrollView.showsVerticalScrollIndicator = false
self.addSubview(scrollView)
let cal = Calendar.current
if appDelegate.eventType != 0 {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-DD"
appDelegate.scheduleAt = formatter.date(from: "2017-01-31")!
currentDate = appDelegate.scheduleAt!
}
getNextMonth = cal.date(byAdding: .month, value: 1, to: cal.startOfDay(for: currentDate))
getPrevMonth = cal.date(byAdding: .month, value: -1, to: cal.startOfDay(for: currentDate))
currentMonthView = CalCollectionView(frame: CGRect(x: screenWidth, y: 0, width: screenWidth, height: screenHeight), selDate: currentDate)
nextMonthView = CalCollectionView(frame: CGRect(x: screenWidth * 2, y: 0, width: screenWidth, height: screenHeight), selDate: getNextMonth!)
prevMonthView = CalCollectionView(frame: CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight), selDate: getPrevMonth!)
scrollView.addSubview(currentMonthView)
scrollView.addSubview(nextMonthView)
scrollView.addSubview(prevMonthView)
}
func scrollViewDidScroll(_ scrollView:UIScrollView) {
let pos:CGFloat = scrollView.contentOffset.x / scrollView.bounds.size.width
let deff:CGFloat = pos - 1.0
if fabs(deff) >= 1.0 {
if (deff > 0) {
self.showNextView()
} else {
self.showPrevView()
}
}
}
func showNextView (){
currentMonth += 1
if( currentMonth > 12 ){
currentMonth = 1
currentYear += 1
}
let tmpView = currentMonthView
currentMonthView = nextMonthView
nextMonthView = prevMonthView
prevMonthView = tmpView
let nextYearAndMonth = self.getNextYearAndMonth()
nextMonthView.setUpDays(nextYearAndMonth)
self.resetContentOffSet()
}
func showPrevView () {
currentMonth -= 1
if( currentMonth == 0 ){
currentMonth = 12
currentYear -= 1
}
let tmpView = currentMonthView
currentMonthView = prevMonthView
prevMonthView = nextMonthView
nextMonthView = tmpView
let prevYearAndMonth = self.getPrevYearAndMonth()
prevMonthView.setUpDays(prevYearAndMonth)
//position調整
self.resetContentOffSet()
}
func resetContentOffSet () {
//position調整
prevMonthView.frame = CGRect(x: 0, y: 0, width: frame.size.width,height: frame.size.height)
currentMonthView.frame = CGRect(x: frame.size.width, y: 0, width: frame.size.width,height: frame.size.height)
nextMonthView.frame = CGRect(x: frame.size.width * 2.0, y: 0, width: frame.size.width,height: frame.size.height)
let scrollViewDelegate:UIScrollViewDelegate = scrollView.delegate!
scrollView.delegate = nil
//delegateを呼びたくないので
scrollView.contentOffset = CGPoint(x: frame.size.width , y: 0.0)
scrollView.delegate = scrollViewDelegate
}
func getNextYearAndMonth () -> Date {
var next_year:Int = currentYear
var next_month:Int = currentMonth + 1
if next_month > 12 {
next_month = 1
next_year += 1
}
let cal = Calendar.current
let increaseYear = cal.date(byAdding: .year, value: next_year, to: cal.startOfDay(for: currentDate))
let increaseMonth = cal.date(byAdding: .month, value: next_month, to: cal.startOfDay(for: increaseYear!))
return increaseMonth!
}
func getPrevYearAndMonth () -> Date {
var prev_year:Int = currentYear
var prev_month:Int = currentMonth - 1
if prev_month == 0 {
prev_month = 12
prev_year -= 1
}
let cal = Calendar.current
let decreaseYear = cal.date(byAdding: .year, value: prev_year, to: cal.startOfDay(for: currentDate))
let decreaseMonth = cal.date(byAdding: .month, value: prev_month, to: cal.startOfDay(for: decreaseYear!))
return decreaseMonth!
}
}
import UIKit
// CalCollectionView.swift
class CalCollectionView : UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let calMng = CalendarManager()
var collectionView : UICollectionView!
var cellSize: CGFloat = 0.0
let cellMargin: CGFloat = 0
let weekArray = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(frame: CGRect, selDate: Date){
super.init(frame: frame)
self.setUpDays(selDate)
}
func setUpDays(_ selDate: Date){
calMng.currentMonthOfDates = []
calMng.currentDate = selDate
let screenWidth : CGFloat = frame.size.width
let screenHeight : CGFloat = 290
// CollectionViewのレイアウトを生成.
let layout = UICollectionViewFlowLayout()
// Cell一つ一つの大きさ.
cellSize = frame.size.width / 7
layout.itemSize = CGSize(width: cellSize, height: 40)
// CollectionViewを生成.
collectionView = UICollectionView(frame: CGRect(x: screenWidth, y: 0, width: frame.size.width, height: CGFloat(screenHeight)), collectionViewLayout: layout)
collectionView.backgroundColor = UIColor.white
collectionView.isScrollEnabled = false
collectionView.center = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
// Cellに使われるクラスを登録.
collectionView.register(CalCollectionCell.self, forCellWithReuseIdentifier: "calCell")
collectionView.delegate = self
collectionView.dataSource = self
collectionView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
self.addSubview(collectionView)
}
// セクションの数
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 2
}
// Cellの総数を返す
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// Section毎にCellの総数を変える.
if section == 0 {
return 7
} else {
return calMng.daysAcquisition()
}
}
// セルの内容を表示
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell: CalCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "calCell", for: indexPath as IndexPath) as! CalCollectionCell
if indexPath.row % 7 == 0 {
cell.textLabel?.textColor = UIColor.lightRed()
} else if indexPath.row % 7 == 6 {
cell.textLabel?.textColor = UIColor.lightBlue()
} else {
cell.textLabel?.textColor = UIColor.gray
}
if indexPath.section == 0 {
cell.textLabel?.text = weekArray[indexPath.row]
} else {
let day = calMng.conversionDateFormat(indexPath: indexPath as NSIndexPath)
let ordinalityDay = calMng.ordinalityDay() - 2
// 月の日数
let daysOfMonth = calMng.daysOfMonth()
let lastRow = ordinalityDay + daysOfMonth
if indexPath.row > ordinalityDay {
// 現在のセルアイテムの日付を取得
let itemDay = calMng.currentMonthOfDates[indexPath.row]
// イベント予定日と同じかどうかをチェック
let isSelectedDay = calMng.compareDate(selDate: itemDay)
if isSelectedDay == true {
appDelegate.selCalDayID = indexPath.row
cell.imgView.image = UIImage(named: "cal-bg")
cell.textLabel?.textColor = UIColor.white
cell.textLabel?.text = day
} else {
cell.textLabel?.text = day
cell.imgView.image = UIImage()
}
} else {
cell.isUserInteractionEnabled = false
cell.textLabel?.text = nil
}
if indexPath.row > lastRow {
cell.isUserInteractionEnabled = false
cell.textLabel?.text = nil
}
}
return cell
}
//セルの垂直方向のマージンを設定
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return cellMargin
}
//セルの水平方向のマージンを設定
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return cellMargin
}
// セルをタップした時の処理
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.section == 1 {
// タップしたセルアイテムの日付を取得
let selItemDay = calMng.currentMonthOfDates[indexPath.row]
// イベント予定日と同じかどうかをチェック
let isSelectedDate = calMng.compareDate(selDate: selItemDay)
if isSelectedDate == false {
// 現状の選択したIDを一旦保持
let tmpItemID = appDelegate.selCalDayID
// 選択したIDを新規にセット
appDelegate.selCalDayID = indexPath.row
// 日付の内容を変更
appDelegate.scheduleAt = calMng.currentMonthOfDates[indexPath.row]
// 古いセルをリロード
reloadItem(selItem: tmpItemID!)
// 新しいセルをリロード
reloadItem(selItem: appDelegate.selCalDayID!)
}
}
}
func reloadItem(selItem: Int) {
collectionView.reloadItems(at: [IndexPath(row: selItem, section: 1)])
}
}
extension UIColor {
class func lightBlue() -> UIColor {
return UIColor(red: 50.0 / 255, green: 149.0 / 255, blue: 218.0 / 255, alpha: 1.0)
}
class func lightRed() -> UIColor {
return UIColor(red: 222.0 / 255, green: 32 / 255, blue: 63 / 255, alpha: 1.0)
}
}
import UIKit
// CalCollectionCell.swift
class CalCollectionCell: UICollectionViewCell {
var textLabel : UILabel?
var imgView : UIImageView!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override init(frame: CGRect) {
super.init(frame: frame)
// UILabelを生成.
textLabel = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: frame.height))
textLabel?.textAlignment = NSTextAlignment.center
imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: 34, height: 34))
imgView.center = CGPoint(x: frame.width / 2, y: frame.height / 2)
imgView.contentMode = .scaleAspectFit
// Cellに追加.
self.contentView.addSubview(imgView)
self.contentView.addSubview(textLabel!)
}
}
import UIKit
// CalendarManager.swift
class CalendarManager {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
//表記する月の配列
var currentMonthOfDates = [Date]()
// カレンダー表示用
var currentDate = Date()
let daysPerWeek: Int = 7
var numberOfItems: Int!
var cal = Calendar.current
//月ごとのセルの数を返すメソッド
func daysAcquisition() -> Int {
// 週の数
let numOfWeeks = Calendar.current.range(of: .weekOfMonth, in: .month, for: firstDateOfMonth())!.count
// 週の数 x 列の数
numberOfItems = numOfWeeks * daysPerWeek
return numberOfItems
}
//月の初日を取得
func firstDateOfMonth() -> Date {
var components = cal.dateComponents([.year, .month, .day], from:currentDate)
components.day = 1
let firstDateMonth = cal.date(from: components)!
return firstDateMonth
}
// 月の初日が週の何日目かを計算
func ordinalityDay() -> Int {
let ordinalityDay = cal.ordinality(of: .day, in: .weekOfMonth, for: firstDateOfMonth())!
return ordinalityDay
}
// 月の日数
func daysOfMonth() -> Int {
let year = cal.component(.year, from: currentDate)
let month = cal.component(.month, from: currentDate)
let date = cal.date(from: DateComponents(year: year, month: month))!
// 日数
let days = cal.range(of: .day, in: .month, for: date)?.count
return days!
}
// ⑴表記する日にちの取得
func dateForCellAtIndexPath(numberOfItems: Int) {
// ①「月の初日が週の何日目か」を計算する
let ordinalityOfFirstDay = cal.ordinality(of: .day, in: .weekOfMonth, for: firstDateOfMonth())
for i in 0 ..< numberOfItems {
// ②「月の初日」と「indexPath.item番目のセルに表示する日」の差を計算する
var dateComponents = DateComponents()
dateComponents.day = i - (ordinalityOfFirstDay! - 1)
// ③ 表示する月の初日から②で計算した差を引いた日付を取得
let date = cal.date(byAdding: dateComponents, to: firstDateOfMonth())!
// ④配列に追加
currentMonthOfDates.append(date)
}
}
// ⑵表記の変更
func conversionDateFormat(indexPath: NSIndexPath) -> String {
dateForCellAtIndexPath(numberOfItems: numberOfItems)
let formatter = DateFormatter()
formatter.dateFormat = "d"
return formatter.string(from: currentMonthOfDates[indexPath.row])
}
func compareDate(selDate: Date) -> Bool {
let isSameDay = cal.isDate(appDelegate.scheduleAt!, inSameDayAs: selDate)
return isSameDay
}
}