Last active
June 3, 2016 16:16
-
-
Save amarcadet/9a53a4805a5c313168778c270bd03480 to your computer and use it in GitHub Desktop.
Self sizing cell with Auto Layout (ObjC and Swift)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import UIKit; | |
@interface TableViewCell : UITableViewCell | |
+ (instancetype)cell; | |
+ (instancetype)cellWithOwner:(id)owner; | |
+ (instancetype)cellWithOwner:(id)owner options:(NSDictionary *)options; | |
- (CGFloat)rowHeightInTableView:(UITableView *)tableView; | |
+ (NSString *)reuseIdentifier; | |
+ (NSString *)nibName; | |
+ (UINib *)nib; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@implementation TableViewCell | |
#pragma mark - Init | |
+ (instancetype)cell | |
{ | |
return [self cellWithOwner:nil]; | |
} | |
+ (instancetype)cellWithOwner:(id)owner | |
{ | |
return [self cellWithOwner:owner options:nil]; | |
} | |
+ (instancetype)cellWithOwner:(id)owner options:(NSDictionary *)options | |
{ | |
NSArray *items = [[self nib] instantiateWithOwner:owner options:options]; | |
if (items.count == 0) { | |
[NSException raise:NSInternalInconsistencyException format:@"%@ xib must have exactly one cell in it", NSStringFromClass([self class])]; | |
} | |
id cell = items.firstObject; | |
if (![cell isKindOfClass:[self class]]) { | |
[NSException raise:NSInternalInconsistencyException format:@"Cell must be an instance of class %@", NSStringFromClass([self class])]; | |
} | |
return cell; | |
} | |
#pragma mark - Row height | |
// http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights/18746930#18746930 | |
// this was needed for iOS 7 because autolayout cells with automatic dimension are not available | |
// but also needed for iOS 8 because there's a bug with AL cells when pushing a view controller | |
// the tableView refresh cells height using the cached estimated value, thus the tableView scroll up | |
- (CGFloat)rowHeightInTableView:(UITableView *)tableView | |
{ | |
// Make sure the constraints have been set up for this cell, since it may have just been created from scratch. | |
// Use the following lines, assuming you are setting up constraints from within the cell's updateConstraints method: | |
[self setNeedsUpdateConstraints]; | |
[self updateConstraintsIfNeeded]; | |
// Set the width of the cell to match the width of the table view. This is important so that we'll get the | |
// correct cell height for different table view widths if the cell's height depends on its width (due to | |
// multi-line UILabels word wrapping, etc). We don't need to do this above in -[tableView:cellForRowAtIndexPath] | |
// because it happens automatically when the cell is used in the table view. | |
// Also note, the final width of the cell may not be the width of the table view in some cases, for example when a | |
// section index is displayed along the right side of the table view. You must account for the reduced cell width. | |
self.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(tableView.bounds), CGRectGetHeight(self.bounds)); | |
// Do the layout pass on the cell, which will calculate the frames for all the views based on the constraints. | |
// (Note that you must set the preferredMaxLayoutWidth on multi-line UILabels inside the -[layoutSubviews] method | |
// of the UITableViewCell subclass, or do it manually at this point before the below 2 lines!) | |
[self setNeedsLayout]; | |
[self layoutIfNeeded]; | |
// Get the actual height required for the cell's contentView | |
CGFloat height = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; | |
// Add an extra point to the height to account for the cell separator, which is added between the bottom | |
// of the cell's contentView and the bottom of the table view cell. | |
height += 1.0f; | |
return height; | |
} | |
#pragma mark - Static | |
+ (NSString *)reuseIdentifier | |
{ | |
return NSStringFromClass([self class]); | |
} | |
+ (NSString *)nibName | |
{ | |
return NSStringFromClass([self class]); | |
} | |
+ (UINib *)nib | |
{ | |
return [UINib nibWithNibName:[self nibName] bundle:nil]; | |
} | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { | |
private enum Section: Int { | |
case Title = 0 | |
case Properties = 1 | |
case Infos = 2 | |
case Contacts = 3 | |
} | |
@IBOutlet weak var tableView: UITableView! | |
// use R.swift to get the cell from nib | |
private let layoutCell = R.nib.myCustomCell.firstView(owner: nil)! | |
private var rowsHeight = [NSIndexPath: CGFloat]() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
tableView.registerNib(R.nib.myCustomCell) | |
} | |
// MARK: Rows height | |
private func rowHeight(atIndexPath indexPath: NSIndexPath) -> CGFloat { | |
if let rowHeight = rowsHeight[indexPath] { return rowHeight } | |
guard let section = Section(rawValue: indexPath.section) else { return 44 } | |
let rowHeight: CGFloat = { | |
switch section { | |
case .Title: | |
// configure the cell | |
return layoutCell.rowHeight(inTableView: tableView) | |
case .Properties: | |
// configure the cell | |
return layoutCell.rowHeight(inTableView: tableView) | |
case .Infos: | |
// configure the cell | |
return layoutCell.rowHeight(inTableView: tableView) | |
case .Contacts: | |
// configure the cell | |
return layoutCell.rowHeight(inTableView: tableView) | |
} | |
}() | |
self.setHeight(rowHeight, forRowAtIndexPath: indexPath) | |
return rowHeight | |
} | |
private func setHeight(height: CGFloat, forRowAtIndexPath indexPath: NSIndexPath) { | |
rowsHeight[indexPath] = height | |
} | |
// MARK: UITableViewDelegate & UITableViewDataSource | |
func numberOfSectionsInTableView(tableView: UITableView) -> Int { | |
return 4 | |
} | |
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
guard let section = Section(rawValue: section) else { return 0 } | |
switch section { | |
case .Title: return 1 | |
case .Properties: return propertyViewModels.count | |
case .Infos: return infoViewModels.count | |
case .Contacts: return contactViewModels.count | |
} | |
} | |
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { | |
guard let section = Section(rawValue: indexPath.section) else { return UITableViewCell() } | |
let cell = tableView.dequeueReusableCellWithIdentifier(R.reuseIdentifier.myCustomCell)! | |
// configure cell | |
return cell | |
} | |
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { | |
return self.rowHeight(atIndexPath: indexPath) | |
} | |
} | |
// MARK: - Cell | |
class TableViewCell: UITableViewCell { | |
// http://stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights/18746930#18746930 | |
// this was needed for iOS 7 because autolayout cells with automatic dimension are not available | |
// but also needed for iOS 8 because there's a bug with AL cells when pushing a view controller | |
// the tableView refresh cells height using the cached estimated value, thus the tableView scroll up | |
@warn_unused_result | |
func rowHeight(inTableView tableView: UITableView) -> CGFloat { | |
// Make sure the constraints have been set up for this cell, since it may have just been created from scratch. | |
// Use the following lines, assuming you are setting up constraints from within the cell's updateConstraints method: | |
self.setNeedsUpdateConstraints() | |
self.updateConstraintsIfNeeded() | |
// Set the width of the cell to match the width of the table view. This is important so that we'll get the | |
// correct cell height for different table view widths if the cell's height depends on its width (due to | |
// multi-line UILabels word wrapping, etc). We don't need to do this above in -[tableView:cellForRowAtIndexPath] | |
// because it happens automatically when the cell is used in the table view. | |
// Also note, the final width of the cell may not be the width of the table view in some cases, for example when a | |
// section index is displayed along the right side of the table view. You must account for the reduced cell width. | |
self.bounds = CGRect(x: 0, y: 0, width: tableView.bounds.width, height: self.bounds.height) | |
// Do the layout pass on the cell, which will calculate the frames for all the views based on the constraints. | |
// (Note that you must set the preferredMaxLayoutWidth on multi-line UILabels inside the -[layoutSubviews] method | |
// of the UITableViewCell subclass, or do it manually at this point before the below 2 lines!) | |
self.setNeedsLayout() | |
self.layoutIfNeeded() | |
// Get the actual height required for the cell's contentView | |
var height = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height | |
// Add an extra point to the height to account for the cell separator, which is added between the bottom | |
// of the cell's contentView and the bottom of the table view cell. | |
height += 1 | |
return height | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment