- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{// Determine which reuse identifier should be used for the cell at this// index path, depending on the particular layout required (you may have// just one, or may have many).NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.// Note that this method will init and return a new cell if there isn't// one available in the reuse pool, so either way after this line of// code you will have a cell with the correct constraints ready to go.UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:// cell.textLabel.text = someTextForThisCell;// ...
// 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:[cell setNeedsUpdateConstraints];[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the// preferredMaxLayoutWidth needs to be set correctly. Do it at this// point if you are NOT doing it within the UITableViewCell subclass// -[layoutSubviews] method. For example:// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{// Determine which reuse identifier should be used for the cell at this// index path.NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse// identifier, creating a cell and storing it in the dictionary if one// hasn't already been added for the reuse identifier. WARNING: Don't// call the table view's dequeueReusableCellWithIdentifier: method here// because this will result in a memory leak as the cell is created but// never returned from the tableView:cellForRowAtIndexPath: method!UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];if (!cell) {cell = [[YourTableViewCellClass alloc] init];[self.offscreenCells setObject:cell forKey:reuseIdentifier];}
// Configure the cell with content for the given indexPath, for example:// cell.textLabel.text = someTextForThisCell;// ...
// 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:[cell setNeedsUpdateConstraints];[cell 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.cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.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 multiline UILabels inside the// -[layoutSubviews] method of the UITableViewCell subclass, or do it// manually at this point before the below 2 lines!)[cell setNeedsLayout];[cell layoutIfNeeded];
// Get the actual height required for the cell's contentViewCGFloat height = [cell.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.0;
return height;}
// NOTE: Set the table view's estimatedRowHeight property instead of// implementing the below method, UNLESS you have extreme variability in// your row heights and you notice the scroll indicator "jumping"// as you scroll.- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{// Do the minimal calculations required to be able to return an// estimated row height that's within an order of magnitude of the// actual height. For example:if ([self isTallCellAtIndexPath:indexPath]) {return 350.0;} else {return 40.0;}}
#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/*** A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.** Many thanks to @smileyborg and @wildmonkey** @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights*/@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/*** Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.** @param name Name of the nib file.** @return collection view cell for using to calculate content based height*/+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/*** Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass** @param block Render the model data to your UI elements in this block** @return Calculated constraint derived height*/- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/*** Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds*/- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
@end
class YourRootViewController {
@IBOutlet var container: UIView! //container mentioned in step 4
override func viewDidLoad() {
super.viewDidLoad()
var lastView: UIView?for data in yourDataSource {
var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)UITools.addViewToTop(container, child: cell.view, sibling: lastView)lastView = cell.view//Insert code here to populate your cell}
if(lastView != nil) {container.addConstraint(NSLayoutConstraint(item: lastView!,attribute: NSLayoutAttribute.Bottom,relatedBy: NSLayoutRelation.Equal,toItem: container,attribute: NSLayoutAttribute.Bottom,multiplier: 1,constant: 0))}
///Add a refresh control, if you want - it seems to work fine in our app:var refreshControl = UIRefreshControl()container.addSubview(refreshControl!)}}
下面是ViewToTopUITools.add代码:
class UITools {///Add child to container, full width of the container and directly under sibling (or container if sibling nil):class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil){child.setTranslatesAutoresizingMaskIntoConstraints(false)container.addSubview(child)
//Set left and right constraints so fills full horz width:
container.addConstraint(NSLayoutConstraint(item: child,attribute: NSLayoutAttribute.Leading,relatedBy: NSLayoutRelation.Equal,toItem: container,attribute: NSLayoutAttribute.Left,multiplier: 1,constant: 0))
container.addConstraint(NSLayoutConstraint(item: child,attribute: NSLayoutAttribute.Trailing,relatedBy: NSLayoutRelation.Equal,toItem: container,attribute: NSLayoutAttribute.Right,multiplier: 1,constant: 0))
//Set vertical position from last item (or for first, from the superview):container.addConstraint(NSLayoutConstraint(item: child,attribute: NSLayoutAttribute.Top,relatedBy: NSLayoutRelation.Equal,toItem: sibling == nil ? container : sibling,attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,multiplier: 1,constant: 0))}}
Add a Table View to your view controller and use auto layout to pin it to the four sides. Then drag a Table View Cell onto the Table View. And onto the Prototype cell, drag a Label. Use auto layout to pin the label to the four edges of the content view of the Table View Cell.
Important note:
Auto layout works together with the important two lines of code I mentioned above. If you don't use auto layout it isn't going to work.
Other IB settings
Custom class name and Identifier
Select the Table View Cell and set the custom class to be MyCustomCell (the name of the class in the Swift file we added). Also set the Identifier to be cell (the same string that we used for the cellReuseIdentifier in the code above.
Zero Lines for Label
Set the number of lines to 0 in your Label. This means multi-line and allows the label to resize itself based on its content.
Hook Up the Outlets
Control drag from the Table View in the storyboard to the tableView variable in the ViewController code.
Do the same for the Label in your Prototype cell to the myCellLabel variable in the MyCustomCell class.
Finished
You should be able to run your project now and get cells with variable heights.
Notes
This example only works for iOS 8 and after. If you are still needing to support iOS 7 then this won't work for you.
Your own custom cells in your future projects will probably have more than a single label. Make sure that you get everything pinned right so that auto layout can determine the correct height to use. You may also have to use vertical compression resistance and hugging. See this article for more about that.
If you are not pinning the leading and trailing (left and right) edges, you may also need to set the label's preferredMaxLayoutWidth so that it knows when to line wrap. For example, if you had added a Center Horizontally constraint to the label in the project above rather than pin the leading and trailing edges, then you would need to add this line to the tableView:cellForRowAtIndexPath method:
Terminating app due to uncaught exception'NSInternalInconsistencyException', reason: 'table view row heightmust not be negative - provided height for index path (<NSIndexPath:0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'...some other lines...
libc++abi.dylib: terminating with uncaught exception of typeNSException