32936


下記の2ページを参考にスワイプで切替えるカレンダーを作成したのですが、CollectionViewCellの選択をうまく解除できません。  
<http://qiita.com/kitanoow/items/65b1418527eabf31e45b>  
<http://qiita.com/sakuran/items/3c2c9f22cbcbf4aff731>

**実装したいこと**  
カレンダーの内容として以下のものを実装したいです。

  1. 左右にスワイプで月ごとのカレンダーを切替える
  2. 日付をタップしたセルの背景に予め用意した画像を表示
  3. 同月内の日付をタップしたら過去に選択したセルの画像表示を解除
  4. 他の月に移動して日付をタップしたら過去に選択した他月のセルの画像表示を解除

**解決したい問題**  
上記の1から3まではなんとか動かせるようになったのですが、問題なのは4番の1画面進んで1画面戻った場合の下記のような挙動です。

こんなイメージで、スワイプしたら画面が左右にスクロールするものです。  
[前の月] [現在の月] [次の月]

[現在の月]セルを選択  
セルの背景画像を表示  
↓  
[次の月]に1画面移動  
セルを選択→セルの背景画像を表示  
↓  
先程の[現在の月]に1画面戻る  
選択したセルの背景が解除されていない

[現在の月]→[次の月]→[次の月]と2画面移動した後に戻る場合には選択が解除されています。  
1画面進んで、1画面戻る場合には既に生成されたCollectionViewがうまく更新できていないからだとは思うのですが、どのようにすれば解決できるのか分かりません。

どうぞよろしくお願いいたします。  
以下はソースコードです。

[code]

    // AppDelegat.swift
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
    
        var scheduleAt : Date?
        var eventType = 1
    
        var selCalDayID : Int?
    }
    
[/code]

* * *
[code]

    // 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()
        }
    }
    
[/code]

* * *
[code]

    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!
        }
    }
    
[/code]

* * *
[code]

    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)
        }
    }
    
[/code]

* * *
[code]

    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!)
        }
    }
    
[/code]

* * *
[code]

    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
        }
    }
    
[/code]


CC BY-SA 3.0
2017-02-25T02:01:09.723
32933
2017-02-25T05:19:58.547
15155
question
swift ios swift3
Swift3 スワイプで切替えるカレンダーでCollectionViewCellの選択をうまく解除できない


あなたのコードの中で、日付のタップを処理しているのは`CalCollectionView.(_:didSelectItemAt:)`中のこの部分ですね。

[code]

            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!)
            }
    
[/code]

`appDelegate.selCalDayID =
indexPath.row`という行からみて、選択した日付を表す情報としては、単に`indexPath.row`を保存しているだけで、
**それがどの`UICollectionView`の`indexPath.row`なのかを保存していません** 。

上記のコード中で`reloadItem(selItem:
tmpItemID!)`の中身は単に現在の月を表す`UICollectionView`中のセルをリロードしているだけですが、日付が前月を表しているのなら`tmpItemID`も前月用の`UICollectionView`中のセルをリロードしないといけないはずです。

* * *

と言うわけで、まずは「選択されている日付」がどの`UICollectionView`の中に存在するのかの情報もどこかに保存しておかないといけません。

[code]

    class AppDelegate: UIResponder, UIApplicationDelegate {
    
        var window: UIWindow?
    
        var scheduleAt : Date?
        var eventType = 1
    
        var selCalDayID : Int?
        var selCalView: CalCollectionView? //<-
    }
    
[/code]

その上で選択日を保存するところ、保存された選択日を利用するところは適切に書き換えてやらないといけません。上記の部分はこんな感じになるでしょうか。

[code]

            if !isSelectedDate {
                // 現状の選択したIDを一旦保持
                let tmpItemID = appDelegate.selCalDayID
                let oldSelCalView = appDelegate.selCalView //<-
                // 選択したIDを新規にセット
                appDelegate.selCalDayID = indexPath.row
                appDelegate.selCalView = self //<-
                // 日付の内容を変更
                appDelegate.scheduleAt = calMng.currentMonthOfDates[indexPath.row]
                // 古いセルをリロード
                if let oldItem = tmpItemID, let oldCollectionView = oldSelCalView?.collectionView {
                    oldCollectionView.reloadItems(at: [IndexPath(row: oldItem, section: 1)])
                }
                // 新しいセルをリロード
                reloadItem(selItem: appDelegate.selCalDayID!)
            }
    
[/code]

(`Bool`値を`true`,`false`と比較するなんていうのはあまりにもダサいので書き換えさせていただきました。(`a > 0`なんて判定を`(a
> 0) ==
true`なんて書いていたら変だと思いませんか?)また、今のあなたのコードでは「選択日がない」という状態は作らないようにしているようですが、それだと`selCalDayID`を`Int?`とOptionalにしている意味もないので、`if-
let`を入れています。)

他にも`selCalDayID`を更新している部分があれば、同じようにしないといけないでしょう。

`collectionView(_:cellForItemAt:)`の中:

[code]

                if isSelectedDay {
                    appDelegate.selCalDayID = indexPath.row
                    appDelegate.selCalView = self //<-
                    cell.imgView.image = UIImage(named: "cal-bg")
                    cell.textLabel?.textColor = UIColor.white
                    cell.textLabel?.text = day
                } else {
                    cell.textLabel?.text = day
                    cell.imgView.image = nil //<- `UIImage()`は動作が未定義で何が起きるかわからない
                }
    
[/code]

* * *

ちなみに ** _2画面移動した後に戻る場合には選択が解除されています_**
の理由は、あなたの`CalendarView`では`nextMonthView`と`prevMonthView`に前月と次月だけの`CalCollectionView`を先読みしているので、2ヶ月分移動させることによって確実に`CalCollectionView`の再作成が行われるためです。


CC BY-SA 3.0
2017-02-25T05:19:58.547
32936
2017-02-25T05:19:58.547
13972
32933
answer