carekit-apple / CareKit

CareKit is an open source software framework for creating apps that help people better understand and manage their health.
https://www.researchandcare.org
Other
2.4k stars 443 forks source link

Sort rows in OCKChecklistTaskView #576

Open macintosh-HD opened 3 years ago

macintosh-HD commented 3 years ago

Is there a way to sort the rows that will be displayed by a synchronised OCKChecklistTaskView? On my view the rows always seem appear in a random order.

I have seen that the OCKTaskController has an updateViewModel(withEvents:) method that should sort the events by their start date, but it does not seem to affect my view even though I have the start dates of my schedule in ascending order.

It would be great to have a similar mechanic like the OCKTaskQuery, where you can specify a sorting order, in the OCKEventQuery as well.

gavirawson-apple commented 3 years ago

Great question! The events should be sorted by start date, is not the case?

Customizing the sort order isn't trivial, but it is possible. We are exploring making changes that do exactly what you mention - sorting the task data in the view controller based on the sort descriptors in the provided task query. Until that lands, you can go this route -

  1. Customize the sorting of the task data before it is displayed by the view.
  2. Customize the sorting of the task data before the store is modified (as a result of say, completing an event).
  3. Create a custom view controller that performs step 1 and 2
// Part 1
class CustomChecklistController: OCKChecklistTaskController {

    override func eventFor(indexPath: IndexPath) -> OCKAnyEvent? {
        // Apply a custom sort to the events before modifying data in the store
        // This ensures we modify the correct event in the store.
        let sortedEvents = taskEvents
            .flatMap { $0.sorted(by: sortByStartDate) }
        let newTaskEvents = OCKTaskEvents(events: sortedEvents)
        return newTaskEvents[indexPath.section][indexPath.row]
    }

}

// Part 2
class CustomChecklistViewSynchronizer: OCKChecklistTaskViewSynchronizer {

    override func updateView(
        _ view: OCKChecklistTaskView,
        context: OCKSynchronizationContext<OCKTaskEvents>
    ) {
        // Apply a custom sort to the events before they are displayed in the view
        let sortedEvents = context.viewModel
            .flatMap { $0.sorted(by: sortByStartDate) }
        let newTaskEvents = OCKTaskEvents(events: sortedEvents)

        let newContext = OCKSynchronizationContext(
            viewModel: newTaskEvents,
            oldViewModel: context.oldViewModel,
            animated: context.animated
        )
        super.updateView(view, context: newContext)
    }
}

// 3. Create and display your new view controller

// Create the custom view controller
let viewController = OCKChecklistTaskViewController(
    controller:  CustomChecklistController(storeManager: storeManager),
    viewSynchronizer: OCKChecklistTaskViewSynchronizer()
)

// Observe task data in the store
viewController.controller.fetchAndObserveEvents(
    forTaskIDs: ["some-task-id"],
    eventQuery: OCKEventQuery(for: Date())
)

Let me know if that works for yah!

macintosh-HD commented 3 years ago

@gavirawson-apple Thanks for the detailed response! I tried to follow follow along, but I got stuck when I tried to initialize a OCKTaskEvents object. The app I'm currently working on uses the stable branch of CareKit. It looks like that in that version some API was not yet publicly accessible, like the OCKTaskEvents initializer. Is there a way around that problem or do I have to use the main branch of CareKit for this?

gavirawson-apple commented 3 years ago

Gotcha! Yeah, working on stable will make accomplishing this a little more roundabout. You can try forking stable and modifying OCKTaskEvents to sort events for each task by their start date. Currently the events are sorted by least recently added.

Happy to help give pointers as you work if that's the route you choose!