Skip to content

Instantly share code, notes, and snippets.

@amarcadet
Last active June 3, 2016 16:16
Show Gist options
  • Save amarcadet/9a53a4805a5c313168778c270bd03480 to your computer and use it in GitHub Desktop.
Save amarcadet/9a53a4805a5c313168778c270bd03480 to your computer and use it in GitHub Desktop.
Self sizing cell with Auto Layout (ObjC and Swift)
@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
@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
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