Upgrade to Pro — share decks privately, control downloads, hide ads and more …

UICollectionViewFlowLayout Sucks

Avatar for bnickel bnickel
November 13, 2014

UICollectionViewFlowLayout Sucks

A post mortem on my many flow layout mistakes and a look at the cool things you can do when you write your own collection view layout. Was followed by demos and review of layout code from https://github.com/bnickel/SEUICollectionViewLayout

Avatar for bnickel

bnickel

November 13, 2014
Tweet

Other Decks in Technology

Transcript

  1. Quick Review • iOS has quite a few ways to

    layout views: • Set it and forget it (setFrame:/setBounds:/setCenter:) • layoutSubviews ([super layoutSubviews]) • autoresizingMask (springs and struts) • AutoLayout (A = B*C +D with priority E) • It also provides two big layout managers: • UITableView (How tall are you?) • UICollectionViewFlowLayout (What size are you? Down or left?)
  2. UITableView and UICollectionView are cool • Big collections of hundreds

    or thousands of objects! • They only create enough views for what can fit on the screen. • Cell reuse. The best of garbage collection at your fingertips. • Self-contained cell layouts. Less interdependencies than AutoLayout.
  3. UITableView and UICollectionViewFlowLayout suck! • You expend huge amounts of

    effort making your models match with its. if indexPath.section != 0 { if indexPath.row != 0 && indexPath.row != items[indexPath.section].count - 1 { // Something just for inner elements of additional sections. } } • You expend huge amounts of effort working around their limitations, often with weird tricks. • Left aligned cells (https://github.com/nilsou/ NHAlignmentFlowLayout) • Single row of cells (http://stackoverflow.com/q/13918276/860000) • Decorations (http://stackoverflow.com/q/17592640/860000)
  4. UICollectionViewFlowLayout: A horror story • We have a simple horizontal

    scrolling collection view with a variety of possible cell types and separators on all but the last cell. • Disclaimer: Some of the approaches we used overlap each other and could be simplified. This was done piecemeal by multiple developers over a span of time.
  5. UICollectionViewFlowLayout: A horror story • Problem 1: We were using

    sizeForCellAtIndexPath: and sometimes two small cells would be placed vertically if next to a big one. • Implemented solution: Subclass UICollectionViewFlowLayout and override layoutAttributesForItemAtIndexPath: to move items with non-zero y-values over and up. • Better solution: Set itemSize = (collectionViewHeight, itemWidth)
  6. UICollectionViewFlowLayout: A horror story • Problem 2: We wanted separator

    lines on all but the last cell. • Implemented solution: Create a single host cell with a with a line to be hidden on the last item. Then implement our own reuse system based on Nibs and renderer ID’s. This had many nasty iterations where we did things like let the last cell contents use the extra width and had many different reuse identifiers for both the collection view cells and the table view cells. • Better Solution: Subclass UICollectionViewFlowLayout and implement a decoration view.
  7. UICollectionViewFlowLayout: A horror story • Yes there are better things

    we could have done but we were new to this and collection views do not lend themselves to obvious configuration. • With a limited and soured impression of collection views, I set off to implement a much more complex UI…
  8. BIG HEADER VIEW SECTION HEADERS SECTION HEADERS IRREGULAR HEADERS IRREGULAR

    HEADERS IRREGULAR HEADERS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS
  9. BIG HEADER VIEW SECTION HEADERS SECTION HEADERS IRREGULAR HEADERS IRREGULAR

    HEADERS IRREGULAR HEADERS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS CELLS CELLS CELLS CELLS CELLS CELLS CELLS
  10. BIG HEADER VIEW D E C O R A T

    I O N S SECTION HEADERS SECTION HEADERS IRREGULAR HEADERS IRREGULAR HEADERS IRREGULAR HEADERS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS BUTTONS CELLS CELLS CELLS CELLS CELLS CELLS CELLS
  11. Lots of rules • Big header above first section. •

    Each section has a header, a decorative circle and a line running its whole length. • Each group of cells has its own header. • If two adjacent groups of cells are small enough to share a row, the do. Big groups wrap and don’t share. • All items on the same row have the same height. • Banner ads get their own line. • No border on the rightmost cell. • When you rotate the screen you want to be in roughly the same spot.
  12. The Layout Handles Everything (Provides Minimal API for model) #import

    "SEUICollectionViewLayout.h" extern NSString * const SEWideFeedSectionHeader; extern NSString * const SEWideFeedItemHeader; extern NSString * const SEWideFeedItemSite; extern NSString * const SEWideFeedHeader; typedef NS_ENUM(NSInteger, SEFeedItemLayoutType) { SEFeedItemLayoutTypeNormalItem, SEFeedItemLayoutTypeBannerAd }; @interface SEWideFeedCollectionViewLayout : SEUICollectionViewLayout @property (nonatomic, assign) CGFloat headerViewHeight; @property (nonatomic, assign) CGFloat normalItemWidth; @property (nonatomic, assign) CGFloat sectionHeaderHeight; @property (nonatomic, assign) CGFloat itemHeaderHeight; @property (nonatomic, assign) CGFloat itemHeaderSpacing; @property (nonatomic, assign) CGFloat itemSpacing; @property (nonatomic, assign) CGFloat rowSpacing; @property (nonatomic, assign) CGSize siteViewSize; @property (nonatomic, assign) CGFloat siteViewSpacing; @property (nonatomic, readonly) CGFloat bannerAdWidth; @end @protocol UICollectionViewDelegateWideFeedLayout <UICollectionViewDelegateSEUILayout> - (BOOL)collectionView:(UICollectionView *)collectionView layout:(SEWideFeedCollectionViewLayout *)collectionViewLayout shouldDisplayHeaderOverItemAtIndexPath:(NSIndexPath *)indexPath; - (NSInteger)collectionView:(UICollectionView *)collectionView layout:(SEWideFeedCollectionViewLayout *)collectionViewLayout numberOfGroupedItemsAtIndexPath:(NSIndexPath *)indexPath; - (SEFeedItemLayoutType)collectionView:(UICollectionView *)collectionView layout:(SEWideFeedCollectionViewLayout *)collectionViewLayout layoutTypeForItemAtIndexPath:(NSIndexPath *)indexPath; - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(SEWideFeedCollectionViewLayout *)collectionViewLayout heightForItemAtIndexPath:(NSIndexPath *)indexPath width:(CGFloat)width; @end
  13. Cells are 100% reusable The original flow layout and mess

    of view reuse code could be dropped allowing the exact same cells to be reused by the two very different layouts. @interface SESingleFeedGroupCollectionViewLayout : SEUICollectionViewLayout @property (nonatomic, assign) CGFloat itemWidth; @property (nonatomic, assign) CGFloat itemSpacing; @property (nonatomic, readonly) CGFloat pageWidth; @end
  14. Cells are 100% reusable The original flow layout and mess

    of view reuse code could be dropped allowing the exact same cells to be reused by the two very different layouts. @interface SESingleFeedGroupCollectionViewLayout : SEUICollectionViewLayout @property (nonatomic, assign) CGFloat itemWidth; @property (nonatomic, assign) CGFloat itemSpacing; @property (nonatomic, readonly) CGFloat pageWidth; @end CELLS CELLS CELLS CELLS CELLS CELLS CELLS CELLS CELLS CELLS
  15. Cells are 100% reusable The original flow layout and mess

    of view reuse code could be dropped allowing the exact same cells to be reused by the two very different layouts. @interface SESingleFeedGroupCollectionViewLayout : SEUICollectionViewLayout @property (nonatomic, assign) CGFloat itemWidth; @property (nonatomic, assign) CGFloat itemSpacing; @property (nonatomic, readonly) CGFloat pageWidth; @end CELLS CELLS CELLS CELLS CELLS CELLS CELLS CELLS CELLS CELLS
  16. So how does it work? 1. The layout gets invalidated.

    2. UICollectionView calls prepareLayout. 3. prepareLayout (your code) lays out UICollectionViewLayoutAttributes (placeholders for view) with setFrame:/setBounds:/setCenter: (Seem familiar?). It also determines contentSize. This can be ugly. 4. UICollectionView manages animation, though it could use some help. 5. When determining what cells to show when scrolling, UICollectionView calls layoutAttributesForElementsInRect: 6. When resizing (adaptive layout or orientation changes), you have a chance to recenter your view with targetContentOffsetForProposedContentOffset:
  17. What’s kind of broken? • UICollectionView update model is challenging.

    • Sections are inserted/removed separately from cells. I just avoid that where possible. • In batch mode, you insert/move cells to their final destination. There’s no simple mapping API to get “Cell move from Index Path X to Y.” You can derive it. I do something easier. • Supplementary view movement animation is a little rough. I fake things by deleting and reinserting all sup views and managing the animation. • Animating both a size change and a scroll position change is broken. I just do one or the other. • Animating from offscreen to on is also flakey. Can be fixed by growing your view. https://github.com/jpsim/UICollectionView-Animation-Bug
  18. My all purpose superclass • https://github.com/bnickel/SEUICollectionViewLayout • SEUICollectionViewLayout manages a

    lot of the challenges related to collection view layouts to take the pain out of animating them. • We can look at SEWideFeedCollectionViewLayout. (Objective-C, closed source) or the simpler SurveyCollectionViewLayout example project (Swift, open source). The latter does really cool stuff in the model layer.