Skip to content

Commit 23ff205

Browse files
committed
Merge pull request #10 from Yelp/YLTableViewCellEstimatedRowHeight
YLTableView 2.0 - YLTableViewCell as protocol
2 parents e952451 + 8e40c29 commit 23ff205

25 files changed

+246
-201
lines changed

Classes/YLTableView.m

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
#import "YLRefreshHeaderView.h"
1313
#import "YLRefreshHeaderViewPrivate.h"
1414
#import "YLTableViewCell.h"
15-
#import "YLTableViewCellPrivate.h"
1615
#import "YLTableViewDataSource.h"
1716
#import "YLTableViewSectionHeaderFooterView.h"
1817

@@ -49,7 +48,7 @@ - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
4948

5049
- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier {
5150
NSAssert(identifier, @"Must have a reuse identifier.");
52-
NSAssert([cellClass isSubclassOfClass:[YLTableViewCell class]], @"You can only use subclasses of YLTableViewCell.");
51+
NSAssert([cellClass conformsToProtocol:@protocol(YLTableViewCell)], @"You can only use cells conforming to YLTableViewCell.");
5352

5453
[super registerClass:cellClass forCellReuseIdentifier:identifier];
5554

@@ -73,20 +72,6 @@ - (void)registerClass:(Class)headerFooterViewClass forHeaderFooterViewReuseIdent
7372
}
7473
}
7574

76-
- (YLTableViewCell *)sizingCellForReuseIdentifier:(NSString *)reuseIdentifier {
77-
NSAssert(reuseIdentifier, @"Must have a reuse identifier.");
78-
NSAssert(self.cellClassForReuseIdentifier[reuseIdentifier], @"You must register a class for this reuse identifier.");
79-
80-
if (!self.sizingCellForReuseIdentifier[reuseIdentifier]) {
81-
Class cellClass = NSClassFromString(self.cellClassForReuseIdentifier[reuseIdentifier]);
82-
YLTableViewCell *const sizingCell = [(YLTableViewCell *)[cellClass alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
83-
sizingCell.sizingCell = YES;
84-
self.sizingCellForReuseIdentifier[reuseIdentifier] = sizingCell;
85-
}
86-
87-
return self.sizingCellForReuseIdentifier[reuseIdentifier];
88-
}
89-
9075
- (YLTableViewSectionHeaderFooterView *)sizingHeaderFooterViewForReuseIdentifier:(NSString *)reuseIdentifier {
9176
NSAssert(reuseIdentifier, @"Must have a reuse identifier.");
9277
NSAssert(self.headerFooterViewClassForReuseIdentifier[reuseIdentifier], @"You must register a class for this reuse identifier.");

Classes/YLTableViewCell.h

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,12 @@
88

99
#import <UIKit/UIKit.h>
1010

11-
extern const CGFloat kUITableViewCellDefaultHeight;
12-
13-
@interface YLTableViewCell : UITableViewCell
11+
@protocol YLTableViewCell <NSObject>
1412

1513
//! Apply a cell model to a cell. Subclasses should implement this to decide how they display a model's content.
1614
- (void)setModel:(nullable id)model;
1715

18-
/*! Set by YLTableView to indicate that this cell will be used solely for sizing and will never be displayed on screen.
19-
*
20-
* For example, subclasses can use this information to optimize away unnecessary configuration.
21-
*/
22-
@property (readonly, assign, nonatomic, getter=isSizingCell) BOOL sizingCell;
16+
//! Estimated height for a cell, called within tableView:estimatedHeightForRowAtIndexPath
17+
+ (CGFloat)estimatedRowHeight;
2318

2419
@end

Classes/YLTableViewCell.m

Lines changed: 0 additions & 56 deletions
This file was deleted.

Classes/YLTableViewCellPrivate.h

Lines changed: 0 additions & 26 deletions
This file was deleted.

Classes/YLTableViewDataSource.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,15 @@ NS_ASSUME_NONNULL_BEGIN
1919
//! Set as the parent view controller of any cells implementing YLTableViewChildViewControllerCell.
2020
@property (weak, nonatomic, nullable) UIViewController *parentViewController;
2121

22+
/*!
23+
Defaults to NO. If you would like to cache the estimated heights for rows, set this to YES.
24+
25+
@note This is only really necessary for iOS 8. If you are using estimated row height + UITableViewAutomaticDimension in iOS 8, there's a bug
26+
when reloadData is called: if the estimated height is different from the real height for the cells, the table view will jump when you scroll
27+
after calling reloadData. To fix this, we can record the actual height as it comes on screen (willDisplayCell), and return this for the
28+
estimated row height. This bug is fixed in iOS 9.
29+
*/
30+
@property (assign, nonatomic) BOOL shouldCacheEstimatedRowHeights;
31+
2232
@end
2333
NS_ASSUME_NONNULL_END

Classes/YLTableViewDataSource.m

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,51 @@
1313
#import "YLTableView.h"
1414
#import "YLTableViewPrivate.h"
1515
#import "YLTableViewCell.h"
16-
#import "YLTableViewCellPrivate.h"
1716
#import "YLTableViewChildViewControllerCell.h"
1817
#import "YLTableViewSectionHeaderFooterView.h"
1918
#import "YLTableViewSectionHeaderFooterViewPrivate.h"
2019

20+
@interface YLTableViewDataSource ()
21+
22+
//! This is used to cache the estimated row heights.
23+
@property (strong, nonatomic) NSMutableDictionary<NSString *, NSNumber *> * indexPathToEstimatedRowHeight;
24+
25+
@end
26+
2127
@implementation YLTableViewDataSource
2228

29+
30+
#pragma mark indexPathToEstimatedRowHeight property methods
31+
32+
- (NSMutableDictionary<NSString *,NSNumber *> *)indexPathToEstimatedRowHeight {
33+
if (!_indexPathToEstimatedRowHeight) {
34+
_indexPathToEstimatedRowHeight = [[NSMutableDictionary alloc] init];
35+
}
36+
37+
return _indexPathToEstimatedRowHeight;
38+
}
39+
2340
#pragma mark Public Helpers
2441

2542
- (void)reloadVisibleCellForModel:(id)model inTableView:(UITableView *)tableView {
2643
for (NSIndexPath *indexPath in [tableView indexPathsForVisibleRows]) {
2744
if ([self tableView:tableView modelForCellAtIndexPath:indexPath] == model) {
28-
[(YLTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] setModel:model];
45+
[(UITableViewCell<YLTableViewCell> *)[tableView cellForRowAtIndexPath:indexPath] setModel:model];
2946
return;
3047
}
3148
}
3249
}
3350

51+
#pragma mark Private Helpers
52+
53+
//! Constructs a cache key for the given index path. UITableView sometimes returns a different subclass, NSMutableIndexPath, so we can't use the index path as the key by itself.
54+
+ (NSString *)_keyForIndexPath:(NSIndexPath *)indexPath {
55+
return [NSString stringWithFormat:@"%ld,%ld", (long)indexPath.section, (long)indexPath.row];
56+
}
57+
3458
#pragma mark Configuration
3559

36-
- (void)tableView:(UITableView *)tableView configureCell:(YLTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
60+
- (void)tableView:(UITableView *)tableView configureCell:(UITableViewCell<YLTableViewCell> *)cell forIndexPath:(NSIndexPath *)indexPath {
3761
[cell setModel:[self tableView:tableView modelForCellAtIndexPath:indexPath]];
3862
}
3963

@@ -77,7 +101,7 @@ - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger
77101
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
78102
NSString *const reuseID = [self tableView:tableView reuseIdentifierForCellAtIndexPath:indexPath];
79103
NSAssert(reuseID != nil, @"Must have a reuse identifier.");
80-
YLTableViewCell *cell = (YLTableViewCell *)[tableView dequeueReusableCellWithIdentifier:reuseID forIndexPath:indexPath];
104+
UITableViewCell<YLTableViewCell> *cell = (UITableViewCell<YLTableViewCell> *)[tableView dequeueReusableCellWithIdentifier:reuseID forIndexPath:indexPath];
81105
[self tableView:tableView configureCell:cell forIndexPath:indexPath];
82106
return cell;
83107
}
@@ -112,16 +136,7 @@ - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger
112136

113137
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
114138
NSAssert([tableView isKindOfClass:[YLTableView class]], @"This can only be the delegate of a YLTableView.");
115-
116-
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
117-
return UITableViewAutomaticDimension;
118-
}
119-
120-
NSString *const reuseID = [self tableView:tableView reuseIdentifierForCellAtIndexPath:indexPath];
121-
YLTableViewCell *cell = [(YLTableView *)tableView sizingCellForReuseIdentifier:reuseID];
122-
[self tableView:tableView configureCell:cell forIndexPath:indexPath];
123-
124-
return [cell heightForWidth:CGRectGetWidth(tableView.bounds) separatorStyle:tableView.separatorStyle];
139+
return UITableViewAutomaticDimension;
125140
}
126141

127142
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
@@ -152,9 +167,26 @@ - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSIntege
152167
}
153168
}
154169

170+
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
171+
NSAssert([tableView isKindOfClass:[YLTableView class]], @"This can only be the delegate of a YLTableView.");
172+
173+
if (self.shouldCacheEstimatedRowHeights && self.indexPathToEstimatedRowHeight[[[self class] _keyForIndexPath:indexPath]]) {
174+
return [self.indexPathToEstimatedRowHeight[[[self class] _keyForIndexPath:indexPath]] floatValue];
175+
}
176+
177+
Class cellClass = NSClassFromString([self tableView:tableView reuseIdentifierForCellAtIndexPath:indexPath]);
178+
NSAssert([cellClass conformsToProtocol:@protocol(YLTableViewCell)], @"You can only use cells conforming to YLTableViewCell.");
179+
return [(id<YLTableViewCell>)cellClass estimatedRowHeight];
180+
}
181+
155182
#pragma mark ChildViewController support
156183

157184
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
185+
186+
if (self.shouldCacheEstimatedRowHeights) {
187+
self.indexPathToEstimatedRowHeight[[[self class] _keyForIndexPath:indexPath]] = @(cell.frame.size.height);
188+
}
189+
158190
UIViewController *parentViewController = self.parentViewController;
159191
if ([cell conformsToProtocol:@protocol(YLTableViewChildViewControllerCell)]) {
160192
NSAssert(parentViewController != nil, @"Must have a parent view controller to support cell %@", cell);

Classes/YLTableViewDataSourceSubclass.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
#import "YLTableViewDataSource.h"
1010

11-
@class YLTableViewCell;
11+
@protocol YLTableViewCell;
1212
@class YLTableViewSectionHeaderFooterView;
1313

1414
NS_ASSUME_NONNULL_BEGIN
@@ -44,7 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
4444
*
4545
* Override this method if you need to do cell-specific configuration (for example, alternating background colors) but call super implementation first.
4646
*/
47-
- (void)tableView:(UITableView *)tableView configureCell:(YLTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath NS_REQUIRES_SUPER;
47+
- (void)tableView:(UITableView *)tableView configureCell:(UITableViewCell<YLTableViewCell> *)cell forIndexPath:(NSIndexPath *)indexPath NS_REQUIRES_SUPER;
4848

4949
//! Override this method to configure headerView to display content in section but call super implementation first.
5050
- (void)tableView:(UITableView *)tableView configureHeader:(YLTableViewSectionHeaderFooterView *)headerView forSection:(NSUInteger)section NS_REQUIRES_SUPER;

Classes/YLTableViewPrivate.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@
88

99
#import "YLTableView.h"
1010

11-
@class YLTableViewCell;
1211
@class YLTableViewSectionHeaderFooterView;
1312

1413
NS_ASSUME_NONNULL_BEGIN
1514
@interface YLTableView ()
1615

17-
//! Returns a cached cell for this reuse identifier. The cell should only be used for sizing purposes, not for display.
18-
- (YLTableViewCell *)sizingCellForReuseIdentifier:(NSString *)reuseIdentifier;
19-
2016
//! Returns a cached section header/footer view for this reuse identifier. The view should only be used for sizing purposes, not for display.
2117
- (YLTableViewSectionHeaderFooterView *)sizingHeaderFooterViewForReuseIdentifier:(NSString *)reuseIdentifier;
2218

Classes/YLTableViewSectionHeaderFooterView/YLTableViewSectionHeaderFooterLabelView.m

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,6 @@ - (void)_installLabelConstraints {
3333
[self.contentLayoutGuideView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_label]|" options:0 metrics:nil views:views]];
3434
}
3535

36-
- (void)layoutSubviews {
37-
[super layoutSubviews];
38-
39-
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
40-
self.label.preferredMaxLayoutWidth = CGRectGetWidth(self.label.bounds);
41-
[super layoutSubviews];
42-
}
43-
}
44-
45-
4636
#pragma mark Text property
4737

4838
- (void)setText:(NSString *)text {

Classes/YLTableViewSectionHeaderFooterView/YLTableViewSectionHeaderFooterView.m

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ - (void)layoutSubviews {
5353
// The layout guide doesn't like being told to have a negative width, so we have to make sure it's at least 0.
5454
self.contentLayoutGuideWidthConstraint.constant = MAX(CGRectGetWidth(self.bounds) - edgeInsets.left - edgeInsets.right, 0);
5555

56-
if ([[[UIDevice currentDevice] systemVersion] floatValue] < 8.0) {
57-
[self.contentView layoutIfNeeded];
58-
}
59-
6056
[super layoutSubviews];
6157
}
6258

YLTableView.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = 'YLTableView'
3-
s.version = '1.0.3'
3+
s.version = '2.0.0'
44
s.license = 'Apache V2'
55
s.summary = 'Yelp iOS table view framework'
66
s.homepage = 'https://github.com/Yelp/YLTableView'
@@ -9,7 +9,7 @@ Pod::Spec.new do |s|
99
s.requires_arc = true
1010

1111
s.platform = :ios
12-
s.ios.deployment_target = '7.0'
12+
s.ios.deployment_target = '8.0'
1313

1414
s.public_header_files = 'Classes/**/*.h'
1515
s.source_files = 'Classes/**/*.{h,m}'

0 commit comments

Comments
 (0)