Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Best way to invoke a layout change for the collection view? #346

Closed
DanielRakh opened this issue Dec 21, 2016 · 11 comments
Closed

Best way to invoke a layout change for the collection view? #346

DanielRakh opened this issue Dec 21, 2016 · 11 comments
Labels

Comments

@DanielRakh
Copy link

DanielRakh commented Dec 21, 2016

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?

@rnystrom
Copy link
Contributor

@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?

@DanielRakh
Copy link
Author

@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.

@rnystrom
Copy link
Contributor

@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.

@jessesquires
Copy link
Contributor

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

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

@rnystrom
Copy link
Contributor

@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?

@jessesquires
Copy link
Contributor

jessesquires commented Dec 22, 2016

Not sure if I understand? 🤔

I meant:

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

So in section controllers you could:

[self.collectionContext invalidateLayout]

@rnystrom
Copy link
Contributor

@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?

@jessesquires
Copy link
Contributor

👍

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+)

@rnystrom
Copy link
Contributor

@jessesquires 8+ yup

@jessesquires
Copy link
Contributor

Closing this in favor of tracking at #360

@DanielRakh
Copy link
Author

DanielRakh commented Dec 24, 2016

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];

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants