swiftswift3calendarswift4jtapplecalendar

JTAppleCalendar Custom cell bug on cellForItemAt date in Swift 3.2


I added JTAppleCalendar inside my project and I want to add some tags in some of my calendar cells. I have success adding them, but when I scroll left or right on my calendar months, cells inside tags disappear, hide, or mix, and when I scroll again and again there is more and more mixing. Do I need any protocols or delegates, etc.? Or, it is just a bug?

How can I fix that bug?

My example GitHub project

My cellForItemAt code:

func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell {
        let cell = calendar.dequeueReusableCell(withReuseIdentifier: "CellView", for: indexPath) as! CellView

        var currentdate = String(describing: myCalendar.date(byAdding: .day, value: 1, to: cellState.date))
        currentdate = currentdate.substring(from: 9, length: 10)


        cell.tagList.tags.removeAll()
        cell.tagList.hide()
        cell.contentView.backgroundColor = nil
        cell.tagList.alpha = 0
        cell.tagList.numberOfRows = 0
        cell.tagList.backgroundColor = UIColor.clear
        cell.tagList.isHidden = true


        var i : Int
        i = 0

        for object in datas {

        i =  i + 1

                let clean = "\(object)".components(separatedBy: "*")

                if clean[0] == currentdate {
                   let gotag : Int
                    gotag = Int(clean[1])!
                    cell.tagList.isHidden = false
                    cell.dayLabel.text = cellState.text
                    cell.contentView.backgroundColor = UIColor.gray

                    let itemName = "Item name  \(i)"


                        cell.tagList.alpha = 1

                        if clean[1] == "1" {

                                cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.orange,textColor: UIColor.white,comesTag: gotag)


                        }else if clean[1] == "2" {

                                cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.green,textColor: UIColor.white,comesTag: gotag)

                        }else if clean[1] == "3" {

                                 cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.brown,textColor: UIColor.white,comesTag: gotag)

                        }else if clean[1] == "4" {

                              cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.black,textColor: UIColor.white,comesTag: gotag)
                        }



            }else{

                cell.tagList.backgroundColor = UIColor.clear
        }





        }


        handleCellConfiguration(cell: cell, cellState: cellState)
        return cell
    }

Bug in action:

https://github.com/LetSwiftDev/CalendarBug/blob/master/calendarbug.gif

Also you can join official JTAppleCalendar chat here https://gitter.im/patchthecode/JTAppleCalendar


Solution

  • Basically, to make a weird "workaround" you should implement the classic UICollectionView (JTAppleCalendar comes from it) willDisplay method, that , as you can see , in theory it should be used to detect cell additions instead of replicate it's content, so to make this re-build of content you can follow the example also explained to the JTAppleCalendar gitHub issues and reported by swift nub here in this page.

    So, your code could be:

    ViewController.swift:

    extension ViewController: JTAppleCalendarViewDelegate, JTAppleCalendarViewDataSource {
        func calendar(_ calendar: JTAppleCalendarView, willDisplay cell: JTAppleCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) {
            var cell = cell as! CellView
            cell = sharedFunctionToConfigureCell(cell: cell, cellState: cellState, date: date)
        }
    
        func calendar(_ calendar: JTAppleCalendarView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTAppleCell {
            var cell = calendar.dequeueReusableCell(withReuseIdentifier: "CellView", for: indexPath) as! CellView
            cell = sharedFunctionToConfigureCell(cell: cell, cellState: cellState, date: date)
            return cell
        }
    
        func sharedFunctionToConfigureCell(cell: CellView, cellState: CellState, date: Date)-> CellView {
            var currentdate = String(describing: myCalendar.date(byAdding: .day, value: 1, to: cellState.date))
            currentdate = currentdate.substring(from: 9, length: 10)
            cell.tagList.tags.removeAll()
            cell.tagList.hide()
            cell.contentView.backgroundColor = nil
            cell.tagList.alpha = 0
            cell.tagList.numberOfRows = 0
            cell.tagList.backgroundColor = UIColor.clear
            cell.tagList.isHidden = true
            var i : Int
            i = 0
            for object in datas {
                i =  i + 1
                let clean = "\(object)".components(separatedBy: "*")
                if clean[0] == currentdate {
                    let gotag : Int
                    gotag = Int(clean[1])!
                    cell.tagList.isHidden = false
                    cell.dayLabel.text = cellState.text
                    cell.contentView.backgroundColor = UIColor.gray
                    let itemName = "Item name  \(i)"
                    cell.tagList.alpha = 1
                    if clean[1] == "1" {
                        cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.orange,textColor: UIColor.white,comesTag: gotag)
                    }else if clean[1] == "2" {
                        cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.green,textColor: UIColor.white,comesTag: gotag)
                    }else if clean[1] == "3" {
                        cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.brown,textColor: UIColor.white,comesTag: gotag)
                    }else if clean[1] == "4" {
                        cell.tagList.addTag(itemName, target: self, tapAction: #selector(ViewController.tap(_:)), longPressAction: #selector(ViewController.tap(_:)),backgroundColor: UIColor.black,textColor: UIColor.white,comesTag: gotag)
                    }
                }else{
                    cell.tagList.backgroundColor = UIColor.clear
                }
            }
            handleCellConfiguration(cell: cell, cellState: cellState)
            return cell
        }
    
        // your other code..
    

    Update (after your tests):

    After your comments, I've decided to analyze in deep your code.

    First of all, there is a little bug in your mainStoryBoard and you can easily correct it replacing DesignableButton (unexistent class) with UIButton as showed in this pic to avoid the error : CalendarBug[9879:1645088] Unknown class _TtC11CalendarBug16DesignableButton in Interface Builder file.

    enter image description here

    After, the full JTAppleCaledar library seems hasn't any problem, in fact the author have extended also the willDisplay delegate that solved many issues around rendering of cells.

    I've found your problem in the TagListView.swift class, more precisely in the method reset.

    TagListView.swift:

    func reset() {
      for tag in tags {
          tag.removeFromSuperview()
      }
      tags = []
      currentRow = 0
      numberOfRows = 0
    }
    

    This method remove all the tags list (the array of labels) from superview but not the other tags added in past to the superview, in other words only tags contained in the array tags. So, to avoid this issue you can reinforce your reset method by adding on line (we know they are UILabel so it's not needed to know all their tag number):

    func reset() {
      for tag in tags {
          tag.removeFromSuperview()
      }
      tags = []
      currentRow = 0
      numberOfRows = 0
      self.subviews.forEach({ if $0 is UILabel { $0.removeFromSuperview()} })
    }
    

    To optimize your code you simply correct this method as:

    func reset(){
        tags = []
        currentRow = 0
        numberOfRows = 0
        self.subviews.forEach({ if $0 is UILabel { $0.removeFromSuperview()} })
    }
    

    Output:

    enter image description here