patchthecode / JTAppleCalendar

The Unofficial Apple iOS Swift Calendar View. Swift calendar Library. iOS calendar Control. 100% Customizable
https://patchthecode.com
MIT License
7.57k stars 812 forks source link

Customizing cell with event in database #201

Closed romans12 closed 7 years ago

romans12 commented 7 years ago

Hi Jay,

Thanks for all the work you've done on the calendar. It's great! I'm new to Swift and am trying to have dates with prior events display differently then cells without events. I know that you've said that logic/loops shouldn't be included in the willDisplayCell function, but I'm struggling to figure out how to change the display on an individual cell when a change to the database occurs.

Right now, I have a function that loops through my database and checks if there are any preexisting dates that match up with the current dates being displayed on the calendar. This seems to be working as when my calendar loads up it displays all the prior events correctly.

Previously, I was having issues with the cells being reused and I saw on one of your documentation pages where I needed to reset the cells. I was able to do that and it fixed it. But, whenever I attempt to add a new event it runs through my function that checks if there on events on the calendar again and it causes the random cell changes again. I can't figure out how to reset the cells so that they don't show up.

I've also been trying to figure out a way to not call the function again when it returns from the segue, but I'm stumped on that.

Here is my function that puts the events on the calendar changing the isHidden value in CellView:

func eventsOnCalendar(_ cell: JTAppleDayCellView, cellState: CellState, date: Date) {
        ref.observe(.value, with: { snapshot in
            for item in snapshot.children {

                let eventItem = Event(snapshot: item as! FIRDataSnapshot)
                let eventDate = self.cal.string(from: cellState.date)

                if eventItem.date == eventDate && cellState.dateBelongsTo == .thisMonth  {
                    print(eventDate, eventItem.date)
                    if (cell as? CellView)?.eventOnDate() == true {
                        (cell as? CellView)?.changeBackground()
                    } else {
                        (cell as? CellView)?.changeBackground()
                    }
                }
            }
        })
    }

My willDisplayCell:

func calendar(_ calendar: JTAppleCalendarView, willDisplayCell cell: JTAppleDayCellView, date: Date, cellState: CellState) {

        (cell as? CellView)?.prepareForReuse()
        (cell as? CellView)?.setupCellBeforeDisplay(cellState, date: date)

    }

My functions in CellView:

func eventOnDate() -> Bool {
        if self.backgroundView.isHidden == false {
            return true
        } else {
            return false
        }
    }

    func prepareForReuse() {
        self.backgroundView.isHidden = true
    }

    func changeBackground() {
        self.backgroundView.isHidden = false
    }

Hope this somewhat makes sense! Thanks!

patchthecode commented 7 years ago

Maybe I should clarify that loop statement.

What happened was: I noticed people would do a loops in there that had heavy work being done. I do not think they realized that the code in the willDisplayCell function will be called for ever cell displayed (since this is a UICollectionView subclass).

So let's say that you have a heavy loop that loops through say 200 items. Since there are 42 cells on the screen (for a full calendar), that loop will be run 200 * 42 times = 8400 times. Not good. If a loop cannot be avoided, then do what good coders do => Put your heavy work on a background thread, and then update the cell on the main thread 👍. I will update the documentation to be more clear on this.

Concerning the code you posted, I do not understand why you are making the backgroundView hidden without checking it first. Wont this make your views flicker? :/ I do not know your code, so i have changed your code below to what I think the correct path should be. I've also change your function names to make it easier for me to read.

var myDataSource: Set<String> = [] // This will hold your dates in string form

Then populate your dataSource

func populateDataSource() {
    ref.observe(.value, with: { snapshot in
        for item in snapshot.children {
            let eventItem = Event(snapshot: item as! FIRDataSnapshot)
            let eventDate = self.cal.string(from: cellState.date)
            myDataSource.insert(eventDate)
        }
    })
}

Delegates

func calendar(_ calendar: JTAppleCalendarView, willDisplayCell cell: JTAppleDayCellView, date: Date, cellState: CellState) {
        (cell as? CellView)?.setupCellBeforeDisplay(cellState, date: date)
}

In your cell

func setupCellBeforeDisplay(cellState: CellState, date: Date) {
   let dateString = self.cal.string(from: date)
   if myDataSource.contains(dateString) && cellState.dateBelongsTo == .thisMonth  {
       print(eventDate, eventItem.date)
       // AT this point here, i do not understand what you are trying to do *******************
       // So i will make a guess
       makeBackgroundVisible()
    } else {
        makeBackgroundHidden()
    }
}

func makeBackgroundHidden() {
    self.backgroundView.isHidden = true
}
func makeBackgroundVisible() {
    self.backgroundView.isHidden = false
}

We have gotten rid of the loop. And made your code clearer.

romans12 commented 7 years ago

Yeah, that makes sense.

I had to switch things a little bit so that I could access my data in the view controller, but that fixed it. Thank you!!

patchthecode commented 7 years ago

cheers. 🍺

ankahathara commented 7 years ago

@romans12 could you please share your code here ?