Iglistkit: Best way to invoke a layout change for the collection view?

Created on 21 Dec 2016  路  11Comments  路  Source: Instagram/IGListKit

What is the best way of implementing a layout change for the collection view. In other words, if i still have the same data but I'd like to just change the presentation of it, what's the best way of doing that? Currently I have checks setup like this:

screen shot 2016-12-21 at 1 57 10 am

and I invoke the layout change by calling [self.adapter reloadDataWithCompletion:nil]

This seems wildly inefficient and lacks an animation of the transition. In a standard UICollectionView you can just call setCollectionViewLayout: and pass in your new layout and have it animate the layout change.

What's the proper way of accomplishing this in IGListKit?

question

Most helpful comment

Not sure if I understand? 馃

I meant:

  • -[UICollectionViewLayout invalidateLayout]
  • -[UICollectionViewLayout invalidateLayoutWithContext:]

So in section controllers you could:

[self.collectionContext invalidateLayout]

All 11 comments

@DanielRakh ya this is a known pain point right now. We do basically the same thing internally atm.

One suggestion might be to pass your layoutMode to a single section controller on init, that way you don't need 3 versions of basically the same thing.

The animated updates are tricky though since calling performUpdates(animated: true) when the data itself hasn't changed wont do anything. Plus, if your data _does_ change, new section controllers will be created for you.

Another thing you could try is having a data model that stores the layout mode somehow, then when the layout changes call adapter.reload(objects: ...). This will pass the new data to your section controllers without destroying them, which you can then use to power your item sizes.

class DataObject : IGListDiffable {
  let text: String
  let layoutMode: LayoutMode

  func diffIdentifier() -> NSObjectProtocol {
    return text
  }

  func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
    guard let object = object as? DataObject else { return false }
    return text == object.text && layoutMode == object.layoutMode
  }
}

// on layout change
let newModels = oldModels.map({ DataObject(text: $0.text, layoutMode: newLayoutMode )})
adapter.reload(objects: newModels)
collectionView.setCollectionViewLayout(newLayout, animated: true)

YMMV tho, I haven't tried this personally. Any mockups or examples that you can show for what you're trying to achieve?

@rnystrom thanks for the thoughtful reply....I'll try out your suggestions. I'm trying to achieve something relatively simple. Just imagine a basic vertical grid (Flow Layout) of items that are all one size. The user then has an option to change the items to a different specified size. There are three specified sizes the items can be in. I'm ok with the changes not being animated, it's more of the issue that I have to reload all of the data to show these changes that I want to avoid.

@DanielRakh in that case you might be able to get by with the model + performUpdates(animated: true) setup. It'll do a reload animation which should look ok too.

@rnystrom -- actually, shouldn't IGListCollectionContext provide layout invalidation methods?

we probably should... (note, this would be a breaking change, technically)

@jessesquires we could definitely add invalidation for section controller or something (the whole section controller or specific indexes). Idk if it'd fix this issue tho, changing the actual UICollectionViewLayout. But maybe that's all this issue would need? The same layout just passing an enum to the section controllers to control the size and invalidate the layout?

Not sure if I understand? 馃

I meant:

  • -[UICollectionViewLayout invalidateLayout]
  • -[UICollectionViewLayout invalidateLayoutWithContext:]

So in section controllers you could:

[self.collectionContext invalidateLayout]

@jessesquires ya I'm saying basically the same thing. But we could make convenience methods that use -[UICollectionViewLayoutInvalidationContext invalidateItemsAtIndexPaths:] internally so you're not invalidating the world.

So if you had a section controller w/ 4 items, you call

[self.collectionContext invlidateLayoutForSectionController:self];

But inside it does something like:

NSInteger section = [self sectionForSectionController:sectionController];
NSArray<NSIndexPath *> *indexPaths = // Create index path for each section + numberOfItems
UICollectionViewLayoutInvalidationContext *context = [UICollectionViewLayoutInvalidationContext new];
[context invalidateItemsAtIndexPaths:indexPaths];
[self.collectionView.colectionViewLayout invalidateLayoutWithContext:context];

Something like that. Maybe too much magic? Or provide this API on top of the basic ones too?

馃憤

No, I think that's good. You're right, probably shouldn't allow sections to invalidate the world (unless they can change the layout, which is also probably a bad idea).

If clients need something like this (section triggering new layout), then the section should probably delegate back to the VC or something.

[context invalidateItemsAtIndexPaths:indexPaths] is iOS 8+, right? (hopefully not 9+)

@jessesquires 8+ yup

Closing this in favor of tracking at #360

I know this is closed but just to update for future reference in case someone else runs into this problem...what worked for me was following the method @rnystrom mentioned above by having my IGListDiffable store the layout information.

Then, iterating through the collection of my models and updating their layout properties:

- (void)headerLayoutButtonDidTouch:(LayoutMode)layout{

  [self.sections enumerateObjectsUsingBlock:^(StickerItemSection * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    obj.layoutMode = layout;
  }];
  [self.adapter reloadObjects:self.sections];
  [self.collectionView setCollectionViewLayout:[UICollectionViewFlowLayout new] animated:YES completion:nil];

}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rnystrom picture rnystrom  路  3Comments

Przemyslaw-Wosko picture Przemyslaw-Wosko  路  3Comments

runmad picture runmad  路  3Comments

shuhrat10 picture shuhrat10  路  3Comments

FazeelAkhtar picture FazeelAkhtar  路  3Comments