Instagram / IGListKit

A data-driven UICollectionView framework for building fast and flexible lists.
https://instagram.github.io/IGListKit/
MIT License
12.87k stars 1.54k forks source link

Accessing other IGListSectionController instances from within current IGListSectionController #530

Closed staticdreams closed 7 years ago

staticdreams commented 7 years ago

New issue checklist

General information

I have an IGListSectionController which can be expanded into 2 cells. Basically it is the same as in Ryan's tutorial @ raywenderlich. I'm also using a property which tracks whether sections is expanded or collapsed. One thing I'm doing on top of it is animating the change.

mar-07-2017 19-32-18

func didSelectItem(at index: Int) {
    UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIViewAnimationOptions.curveEaseInOut, animations: {
        self.expanded = !self.expanded
        self.collectionContext?.reload(self)
    }) { finished in }

}

What I want to do is to collapse all other cells (if there are any currently expanded so it mimics an accordion. How do I access all other IGListSectionController instance to modify the expanded property? Something tells me I should use collectionContext but I'm not sure how.

rnystrom commented 7 years ago

@staticdreams this is a good architecture question. So we have to think about data-flow for this. You want to change "global" state (state of stuff outside of a single section controller) based on the actions of a single section controller.

We don't really recommend reaching outside of a section controller's scope, and don't have any API to do so. There are some strategies to do this though:

  1. NSNotificationCenter even that all other section controllers listen to. 👎 don't do this
  2. An object created at the view controller level that is handed to each section controller that has an API like toggleExpandedSectionControllers and internally iterates all SCs and flips their flags
  3. Reflect expansion state at the model-level that the view controller tracks. Create a MySectionControllerDelegate that the view controller conforms to, send events "upward" from the section controller when cells are tapped. Then the VC does adapter.performUpdates(...). Just make sure your models are updated when handed back to the section controllers.

In Instagram we use a mix of 2 and 3. In most places 2 is the most versatile when trying to do fancy interactions like what you're building. However 3 is the more "proper", uni-directional data flow way to do things.

I'd love to hear about what works best for you!

staticdreams commented 7 years ago

@rnystrom thank you for your reply and suggestions. I went for a classic controller delegate solution, notifying parent view controller (that serves as the adapter). At this point, how do I access and iterate through all sections controllers? I'm looking at adapter and it only allows me to access visibleSectionControllers(), but I need all, not just visible ones.

rnystrom commented 7 years ago

@staticdreams just get the objects then iterate them and get the section controller for each.

Something like:

let sectionControllers = adapter.objects().map { adapter.sectionController(for object:$0) }
staticdreams commented 7 years ago

@rnystrom I was able to get my list of section controllers (there aren't that many, like 10 of them) from your example, but while iterating through and toggling the flag to false I'm getting this:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (2), plus or minus the number of items inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'

Am I missing something? Like to do peformUpdates: somewhere ?

rnystrom commented 7 years ago

@staticdreams how are you hiding the cell? It looks like you're changing what is returned in numberOfItems(), right?

What are you using to actually do the reload? If you're calling collectionContext?.reload(self), you'll need to change the state and reload one-at-a-time since the data source will be changing, and its not going through batch updates.

Another idea is to use adapter.reloadObjects(objects), that will call reload on each section. If taking this route you can update each section controller then call reloadObjects() once at the end w/ an array of objects.

staticdreams commented 7 years ago

@rnystrom Thank you, Ryan! It worked!