Skip to content

Instantly share code, notes, and snippets.

@simonpang
Last active May 30, 2018 08:15
Show Gist options
  • Save simonpang/7147727e7b2a38e2c737837018daf758 to your computer and use it in GitHub Desktop.
Save simonpang/7147727e7b2a38e2c737837018daf758 to your computer and use it in GitHub Desktop.
Dynamic collection view cell size using auto-layout
2018-05-30 13:08:05.132277+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:07.329048+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:09.120296+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:10.334120+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:10.860428+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:13.211666+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:15.438966+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:17.102895+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:17.539026+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:18.844853+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:20.902874+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:22.332761+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:22.627645+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:24.122012+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:24.475678+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:25.512129+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:25.885873+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:28.048902+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:30.458764+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:31.459993+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:31.586576+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:31.877040+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:32.800872+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:32.878281+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:33.182918+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:33.425012+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:34.492225+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:34.492506+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:34.866927+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:35.867912+0800 DemoScrolling[30506:1014563] set cell width to 200
2018-05-30 13:08:36.111121+0800 DemoScrolling[30506:1014563] startLoading
2018-05-30 13:08:36.976462+0800 DemoScrolling[30506:1014563] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndex:]: index 0 beyond bounds for empty array'
*** First throw call stack:
(
0 CoreFoundation 0x000000010ace912b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010a37df41 objc_exception_throw + 48
2 CoreFoundation 0x000000010ad290cc _CFThrowFormattedException + 194
3 CoreFoundation 0x000000010ac18e94 -[__NSArrayM objectAtIndex:] + 148
4 UIKit 0x000000010c604f01 -[_UIFlowLayoutInfo setSize:forItemAtIndexPath:] + 363
5 UIKit 0x000000010c578f86 -[UICollectionViewFlowLayout invalidationContextForPreferredLayoutAttributes:withOriginalAttributes:] + 304
6 UIKit 0x000000010c52e05a -[UICollectionView _checkForPreferredAttributesInView:originalAttributes:] + 591
7 UIKit 0x000000010c52eb62 -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:isFocused:notify:] + 974
8 UIKit 0x000000010c52e78e -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:] + 35
9 UIKit 0x000000010c533d00 -[UICollectionView _updateVisibleCellsNow:] + 4860
10 UIKit 0x000000010c539c21 -[UICollectionView layoutSubviews] + 364
11 UIKit 0x000000010bb2da6d -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1439
12 QuartzCore 0x0000000110bf661c -[CALayer layoutSublayers] + 159
13 QuartzCore 0x0000000110bfa7ad _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 401
14 QuartzCore 0x0000000110b8186c _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 364
15 QuartzCore 0x0000000110bae946 _ZN2CA11Transaction6commitEv + 500
16 QuartzCore 0x0000000110baf694 _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 76
17 CoreFoundation 0x000000010ac8bc07 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
18 CoreFoundation 0x000000010ac8bb5e __CFRunLoopDoObservers + 430
19 CoreFoundation 0x000000010ac70124 __CFRunLoopRun + 1572
20 CoreFoundation 0x000000010ac6f889 CFRunLoopRunSpecific + 409
21 GraphicsServices 0x000000010fd9e9c6 GSEventRunModal + 62
22 UIKit 0x000000010ba5c5d6 UIApplicationMain + 159
23 DemoScrolling 0x0000000109a6ef6f main + 111
24 libdyld.dylib 0x000000010e7b0d81 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
//
// ViewController.m
// DemoScrolling
//
// Created by Simon Pang on 30/5/2018.
// Copyright © 2018 Simon Pang. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
NSMutableDictionary<NSNumber *, NSNumber *> *cachedWidth;
UICollectionViewFlowLayout *globalLayout;
@interface AwesomeCell : UICollectionViewCell
- (void)setCellWidth:(CGFloat) width;
@property (nonatomic, strong) NSLayoutConstraint *cellWidthConstraint;
@end
@implementation AwesomeCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.cellWidthConstraint = [self.contentView.widthAnchor constraintEqualToConstant:0.f];
}
return self;
}
- (void)setCellWidth:(CGFloat) width {
self.cellWidthConstraint.constant = width;
self.cellWidthConstraint.active = YES;
}
- (void)startLoading:(NSIndexPath *)indexPath {
NSLog(@"startLoading");
[NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"set cell width to 200");
cachedWidth[@(indexPath.row)] = @(200);
[self setCellWidth:200];
//[globalLayout invalidateLayout];
UICollectionViewFlowLayoutInvalidationContext *ctx = [UICollectionViewFlowLayoutInvalidationContext new];
[ctx invalidateItemsAtIndexPaths:@[indexPath]];
[globalLayout invalidateLayoutWithContext:ctx];
}];
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// quick
cachedWidth = [[NSMutableDictionary<NSNumber *, NSNumber *> alloc] init];
UICollectionViewFlowLayout *layout = (id) self.collectionView.collectionViewLayout;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.estimatedItemSize = CGSizeMake(100, 120);
globalLayout = layout;
[self.collectionView registerClass:[AwesomeCell class] forCellWithReuseIdentifier:@"cell"];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
AwesomeCell *cell = (id) [collectionView cellForItemAtIndexPath:indexPath];
[cell startLoading: indexPath];
}
#pragma mark UICollectionViewDataSource
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
AwesomeCell *cell = (id) [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor lightGrayColor];
NSNumber *width = cachedWidth[@(indexPath.row)];
if (width) {
[cell setCellWidth:[width floatValue]];
} else {
[cell setCellWidth:100];
}
return cell;
}
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 1000;
}
- (NSInteger)numberOfSections {
return 1;
}
@end
//
// ViewController.m
// DemoScrolling
//
// Created by Simon Pang on 30/5/2018.
// Copyright © 2018 Simon Pang. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@end
NSMutableDictionary<NSNumber *, NSNumber *> *cachedWidth;
UICollectionViewFlowLayout *globalLayout;
@interface AwesomeCell : UICollectionViewCell
@end
@implementation AwesomeCell
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
self.contentView.layer.borderWidth = 5;
self.contentView.layer.borderColor = UIColor.redColor.CGColor;
}
return self;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// quick
cachedWidth = [[NSMutableDictionary<NSNumber *, NSNumber *> alloc] init];
UICollectionViewFlowLayout *layout = (id) self.collectionView.collectionViewLayout;
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
//layout.estimatedItemSize = CGSizeMake(100, 120); // use delegate to return cell size
globalLayout = layout;
[self.collectionView registerClass:[AwesomeCell class] forCellWithReuseIdentifier:@"cell"];
self.collectionView.prefetchDataSource = self;
}
#pragma mark UICollectionViewDelegate
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
AwesomeCell *cell = (id) [collectionView cellForItemAtIndexPath:indexPath];
//[cell startLoading: indexPath];
}
#pragma mark UICollectionViewDataSource
- (__kindof UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
AwesomeCell *cell = (id) [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.contentView.backgroundColor = [UIColor lightGrayColor];
// Trigger loading
NSNumber *width = cachedWidth[@(indexPath.row)];
if (!width) {
[self startLoading: indexPath];
}
return cell;
}
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 1000;
}
- (NSInteger)numberOfSections {
return 1;
}
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *width = cachedWidth[@(indexPath.row)];
if (width) {
return CGSizeMake([width floatValue], 120);
}
return CGSizeMake(100, 120);
}
#pragma mark UICollectionViewDataSourcePrefetching
- (void)collectionView:(UICollectionView *)collectionView prefetchItemsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths {
for (NSIndexPath *indexPath in indexPaths) {
// We must check if the item is cached
NSNumber *width = cachedWidth[@(indexPath.row)];
if (!width) {
NSLog(@"Prefetching %@", indexPath);
[self startLoading:indexPath];
}
}
}
- (void)startLoading:(NSIndexPath *)indexPath {
NSLog(@"startLoading");
[NSTimer scheduledTimerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
NSLog(@"set cell width to 200");
cachedWidth[@(indexPath.row)] = @(200);
[globalLayout invalidateLayout];
// UICollectionViewFlowLayoutInvalidationContext *ctx = [UICollectionViewFlowLayoutInvalidationContext new];
// [ctx invalidateItemsAtIndexPaths:@[indexPath]];
// [globalLayout invalidateLayoutWithContext:ctx];
}];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment