richardtop / CalendarKit

📅 Calendar for Apple platforms in Swift
https://www.youtube.com/watch?v=cJ63-_z1qg8
MIT License
2.49k stars 334 forks source link

Enhancement: Provide a way to update views on the timeline bypassing the Timeline data source #283

Closed RareScrap closed 3 years ago

RareScrap commented 3 years ago

New Issue Checklist

Issue Description

A situation often arises when we need to update data on the TimelineView bypassing the call to the data source (since it can start a network request). Now we have no way to update the data only on the TimelineContainer we need. The only way to force TimelineView to show new EventViews is to reload data on all pages. If we reload the data after receiving the server response, then the TimelineContainer` will refer to the data source again, which will lead to recursive requests.

I suggest adding to the CalendarKit the ability to set the eventdescriptors directly to TimelineView.

Code I'm using with CalendarKit

Now I use this approach inside the TimelineViewPager:

public func updateTimeline(_ date: Date, events: [EventDescriptor]) {
  let timelineController = pagingViewController.viewControllers?.first {
    if let controller = $0 as? TimelineContainerController {
      return controller.timeline.date == date
    }

    return false
  } as? TimelineContainerController

  guard timelineController != nil else { return }

  let day = TimePeriod(beginning: date,
                       chunk: TimeChunk.dateComponents(days: 1))
  let validEvents = events.filter{$0.datePeriod.overlaps(with: day)}
  timelineController!.timeline.appearance = self.timelineViewAppearance // TODO: is this a propper place for setting the appearance?
  timelineController!.timeline.layoutAttributes = validEvents.map(EventLayoutAttributes.init)
}

But this is not the best idea, because there is no way to distinguish which EventViews have been added, which have changed, and which have been removed. I think it will be useful to know in the case when the animation of add / remove occurs.

I propose to discuss this feature.

richardtop commented 3 years ago

Hi, this issue has been considered. In order to get around it I could suggest few approaches:

  1. Use a "smart" datasource object (and assign it to the dataSource property of the DayView). In that DataSource cache the data you receive from the network and use cached data if it's available. Then reloadData is not a problem, as it just returns currently cached events for the day. This is a similar pattern used in UITableView when implementing newsfeeds for social media with infinite scrolling.

  2. Use a unidirectional data flow: Network -> Local Storage -> CalendarKit Ck always reads data from the local storage. Some other object (NetworkHandler / NetworkInterface / SyncManager, etc) decides if it's appropriate time to start a network request. After receiving a request, it's processed and stored in the local storage. CK is just reloaded with the updated data from the local storage.

richardtop commented 3 years ago

So, to clarify this a bit more. Apart from the editing events flow (as shown in the "advanced" example) the datasource should always be in sync with what's displayed on the screen. Similar to the UITableView, if you'd like to have some network fetching logic, it's a better idea to make the DataSource to cache this information.

RareScrap commented 3 years ago

Thanks for the answer. Now I am not making a request to the server in data source. Instead, I am use the cached data, while the new data I fetch in dayView(dayView:willMoveTo:). After which I just call reloadData().

However, I still don't feel comfortable that I'm updating ALL pages instead of the current one.

richardtop commented 3 years ago

Please let me know if this causes performance issues on your volume of data. It should update 2 pages of calendar at most, which should be very fast if the DataSource is implemented correctly