ios increase - Verwenden von Auto Layout in UITableView für dynamische Zellenlayouts und variable Zeilenhöhen




working not (21)

Wie verwenden Sie das automatische Layout innerhalb von UITableViewCell in einer Tabellenansicht, damit der Inhalt und die Untersichten jeder Zelle die Zeilenhöhe (selbst / automatisch) bestimmen, während die reibungslose Scrollleistung erhalten bleibt?


Answers

I had to use dynamic views (setup views and constraints by code) and when I wanted to set preferredMaxLayoutWidth label's width was 0. So I've got wrong cell height.

Then I added

[cell layoutSubviews];

before executing

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

After that label's width was as expected and dynamic height was calculating right.


As long as your layout in your cell is good.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

Update: You should use dynamic resizing introduced in iOS 8.


Here is solution if it is containing text only:

private func estimateFrameForText(text: String) -> CGRect {
    let size = CGSize(width: 300.0, height: 1000)
    let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
    return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17.0)], context: nil)
}

The solution proposed by @smileyborg is almost perfect. If you have a custom cell and you want one or more UILabel with dynamic heights then the systemLayoutSizeFittingSize method combined with AutoLayout enabled returns a CGSizeZero unless you move all your cell constraints from the cell to its contentView (as suggested by @TomSwift here How to resize superview to fit all subviews with autolayout? ).

To do so you need to insert the following code in your custom UITableViewCell implementation (thanks to @Adrian).

- (void)awakeFromNib{
    [super awakeFromNib];
    for (NSLayoutConstraint *cellConstraint in self.constraints) {
        [self removeConstraint:cellConstraint];
        id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
        id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
        NSLayoutConstraint *contentViewConstraint =
        [NSLayoutConstraint constraintWithItem:firstItem
                                 attribute:cellConstraint.firstAttribute
                                 relatedBy:cellConstraint.relation
                                    toItem:seccondItem
                                 attribute:cellConstraint.secondAttribute
                                multiplier:cellConstraint.multiplier
                                  constant:cellConstraint.constant];
        [self.contentView addConstraint:contentViewConstraint];
    }
}

Mixing @smileyborg answer with this should works.


To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.

  • Assign and implement tableview dataSource and delegate
  • Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
  • Implement delegate/dataSource methods (ie heightForRowAt and return a value UITableViewAutomaticDimension to it)

-

Objective C:

// in ViewController.h
#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>

  @property IBOutlet UITableView * table;

@end

// in ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    self.table.dataSource = self;
    self.table.delegate = self;

    self.table.rowHeight = UITableViewAutomaticDimension;
    self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    return UITableViewAutomaticDimension;
}

Schnell:

@IBOutlet weak var table: UITableView!

override func viewDidLoad() {
    super.viewDidLoad()

    // Don't forget to set dataSource and delegate for table
    table.dataSource = self
    table.delegate = self

    // Set automatic dimensions for row height
    table.rowHeight = UITableViewAutomaticDimension
    table.estimatedRowHeight = UITableViewAutomaticDimension
}



// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

For label instance in UITableviewCell

  • Set number of lines = 0 (& line break mode = truncate tail)
  • Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
  • Optional : Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

Note : If you've more than one labels (UIElements) with dynamic length, which should be adjusted according to its content size: Adjust 'Content Hugging and Compression Resistance Priority` for labels which you want to expand/compress with higher priority.


I just did some dumb try and error with the 2 values of rowHeight and estimatedRowHeight and just thought it might provide some debugging insight:

If you set them both OR only set the estimatedRowHeight you will get the desired behavior:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

It's suggested that you do your best to get the correct estimate, but the end result isn't different. It will just affect your performance.

If you only set the rowHeight ie only do:

tableView.rowHeight = UITableViewAutomaticDimension

your end result would not be as desired:

If you set the estimatedRowHeight to 1 or smaller then you will crash regardless of the rowHeight .

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1 

I crashed with the following error message:

Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must 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 type
NSException

Ich habe @ smileyborgs iOS7-Lösung in eine Kategorie eingepackt

Ich entschied mich dafür, diese clevere Lösung von @smileyborg in eine Kategorie UICollectionViewCell+AutoLayoutDynamicHeightCalculation .

Die Kategorie behebt auch die Probleme, die in der Antwort von @ wildmonkey beschrieben sind (Laden einer Zelle aus einer Nib und systemLayoutSizeFittingSize: Zurückgeben von CGRectZero )

Es berücksichtigt keine Zwischenspeicherung, aber passt jetzt zu meinen Bedürfnissen. Fühlen Sie sich frei, es zu kopieren, einzufügen und zu hacken.

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.h

#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 .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

UICollectionViewCell + AutoLayoutDynamicHeightCalculation.m

#import "UICollectionViewCell+AutoLayout.h"

@implementation UICollectionViewCell (AutoLayout)

#pragma mark Dummy Cell Generator

+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
    UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
    [heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
    return heightCalculationCell;
}

#pragma mark Moving Constraints

- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
    [self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
        [self removeConstraint:constraint];
        id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
        id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
        [self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
                                                                     attribute:constraint.firstAttribute
                                                                     relatedBy:constraint.relation
                                                                        toItem:secondItem
                                                                     attribute:constraint.secondAttribute
                                                                    multiplier:constraint.multiplier
                                                                      constant:constraint.constant]];
    }];
}

#pragma mark Height

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
    return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
                                            collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
    NSParameterAssert(block);

    block();

    [self setNeedsUpdateConstraints];
    [self updateConstraintsIfNeeded];

    self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));

    [self setNeedsLayout];
    [self layoutIfNeeded];

    CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return calculatedSize.height;

}

@end

Verwendungsbeispiel:

- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
    CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
        [(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
    }];
    return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}

Zum Glück müssen wir diesen Jazz in iOS8 nicht machen, aber da ist es jetzt!


In my case i have to create a custom cell with a image which is coming from server and can be of any width and height. And two UILabels with dynamic size(both width & height)

i have achieved the same here in my answer with autolayout and programmatically:

Basically above @smileyBorg answer helped but systemLayoutSizeFittingSize never worked for me, In my approach :

1. No use of automatic row height calculation property. 2.No use of estimated height 3.No need of unnecessary updateConstraints. 4.No use of Automatic Preferred Max Layout Width. 5. No use of systemLayoutSizeFittingSize (should have use but not working for me, i dont know what it is doing internally), but instead my method -(float)getViewHeight working and i know what it's doing internally.

Is it possible to have differing heights in a UITableView Cell when I use several different ways of displaying the cell?


(for Xcode 8.x / Xcode 9.x read at the bottom)

Beware of the following issue in in Xcode 7.x, which might be a source of confusion:

Interface Builder does not handle auto-sizing cell set-up properly. Even if your constraints are absolutely valid, IB will still complain and give you confusing suggestions and errors. The reason is that IB is unwilling to change the row's height as your constraints dictate (so that the cell fits around your content). Instead, it keeps the row's height fixed and starts suggesting you change your constraints, which you should ignore .

For example, imagine you've set up everything fine, no warnings, no errors, all works.

Now if you change the font size (in this example I'm changing the description label font size from 17.0 to 18.0).

Because the font size increased, the label now wants to occupy 3 rows (before that it was occupying 2 rows).

If Interface Builder worked as expected, it would resize the cell's height to accommodate the new label height. However what actually happens is that IB displays the red auto-layout error icon and suggest that you modify hugging/compression priorities.

You should ignore these warnings. What you can* do instead is to manually change the row's height in (select Cell > Size Inspector > Row Height).

I was changing this height one click at a time (using the up/down stepper) until the red arrow errors disappear! (you will actually get yellow warnings, at which point just go ahead and do 'update frames', it should all work).

* Note that you don't actually have to resolve these red errors or yellow warnings in Interface Builder - at runtime, everything will work correctly (even if IB shows errors/warnings). Just make sure that at runtime in the console log you're not getting any AutoLayout errors.

In fact trying to always update row height in IB is super annoying and sometimes close to impossible (because of fractional values).

To prevent the annoying IB warnings/errors, you can select the views involved and in Size Inspector for the property Ambiguity choose Verify Position Only

Xcode 8.x / Xcode 9.x seems to (sometimes) be doing things differently than Xcode 7.x, but still incorrectly. For example even when compression resistance priority / hugging priority are set to required (1000), Interface Builder might stretch or clip a label to fit the cell (instead of resizing cell height to fit around the label). And in such a case it might not even show any AutoLayout warnings or errors. Or sometimes it does exactly what Xcode 7.x did, described above.


tableView.estimatedRowHeight = 343.0
tableView.rowHeight = UITableViewAutomaticDimension


If you do you layout programmatically, here is what to consider for iOS 10 using anchors in Swift.

There are three rules/ steps

NUMBER 1: set this two properties of tableview on viewDidLoad, the first one is telling to the tableview that should expect dynamic sizes on their cells, the second one is just to let the app calculate the size of the scrollbar indicator, so it helps for performance.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

NUMBER 2: This is important you need to add the subviews to the contentView of the cell not to the view, and also use its layoutsmarginguide to anchor the subviews to the top and bottom, this is a working example of how to do it.

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setUpViews()
}

private func setUpViews() {

    contentView.addSubview(movieImageView)
    contentView.addSubview(descriptionLabel)
    let marginGuide = contentView.layoutMarginsGuide

    NSLayoutConstraint.activate([
        movieImageView.heightAnchor.constraint(equalToConstant: 80),
        movieImageView.widthAnchor.constraint(equalToConstant: 80),
        movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
        movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),

        descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
        descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
        descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
        descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)

        ])
}

Create a method that will add the subviews and perform the layout, call it in the init method.

NUMBER 3: DON'T CALL THE METHOD:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

If you do it you will override your implementation.

Follow this 3 rules for dynamic cells in tableviews.

here is a working implementation https://github.com/jamesrochabrun/MinimalViewController


Schnelles Beispiel einer UITableViewCell mit variabler Höhe

Für Swift 3 aktualisiert

William Hus schnelle Antwort ist gut, aber es hilft mir, ein paar einfache, aber detaillierte Schritte zu machen, wenn ich etwas zum ersten Mal lernen muss. Das folgende Beispiel ist mein Testprojekt, während ich lerne, ein UITableView mit variablen UITableView zu UITableView . Ich basierte auf diesem grundlegenden UITableView-Beispiel für Swift .

Das fertige Projekt sollte so aussehen:

Erstellen Sie ein neues Projekt

Es kann nur eine Single-View-Anwendung sein.

Fügen Sie den Code hinzu

Fügen Sie Ihrem Projekt eine neue Swift-Datei hinzu. Nennen Sie es MyCustomCell. Diese Klasse enthält die Ausgänge für die Ansichten, die Sie Ihrer Zelle im Storyboard hinzufügen. In diesem grundlegenden Beispiel haben wir nur eine Bezeichnung in jeder Zelle.

import UIKit
class MyCustomCell: UITableViewCell {
    @IBOutlet weak var myCellLabel: UILabel!
}

Wir werden diese Steckdose später anschließen.

Öffnen Sie ViewController.swift und stellen Sie sicher, dass Sie folgenden Inhalt haben:

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // These strings will be the data for the table view cells
    let animals: [String] = [
        "Ten horses:  horse horse horse horse horse horse horse horse horse horse ",
        "Three cows:  cow, cow, cow",
        "One camel:  camel",
        "Ninety-nine sheep:  sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
        "Thirty goats:  goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]

    // Don't forget to enter this in IB also
    let cellReuseIdentifier = "cell"

    @IBOutlet var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        // delegate and data source
        tableView.delegate = self
        tableView.dataSource = self

        // Along with auto layout, these are the keys for enabling variable cell height
        tableView.estimatedRowHeight = 44.0
        tableView.rowHeight = UITableViewAutomaticDimension
    }

    // number of rows in table view
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.animals.count
    }

    // create a cell for each table view row
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
        cell.myCellLabel.text = self.animals[indexPath.row]
        return cell
    }

    // method to run when table view cell is tapped
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        print("You tapped cell number \(indexPath.row).")
    }
}

Wichtige Notiz:

  • Es sind die folgenden zwei Codezeilen (zusammen mit dem automatischen Layout), die die variable Zellenhöhe ermöglichen:

    tableView.estimatedRowHeight = 44.0
    tableView.rowHeight = UITableViewAutomaticDimension
    

Richte das Storyboard ein

Fügen Sie Ihrem View-Controller eine Tabellenansicht hinzu und verwenden Sie das automatische Layout, um es an die vier Seiten zu heften. Ziehen Sie dann eine Tabellenansichtszelle in die Tabellenansicht. Ziehen Sie in der Zelle Prototype ein Label. Verwenden Sie das automatische Layout, um die Beschriftung an die vier Kanten der Inhaltsansicht der Tabellenansichtszelle anzuheften.

Wichtige Notiz:

  • Das automatische Layout funktioniert zusammen mit den oben erwähnten wichtigen zwei Codezeilen. Wenn Sie kein automatisches Layout verwenden, wird es nicht funktionieren.

Andere IB Einstellungen

Name und Kennung der benutzerdefinierten Klasse

Wählen Sie die Tabellenansichtszelle aus, und legen Sie die benutzerdefinierte Klasse als MyCustomCell (der Name der Klasse in der Swift-Datei, die wir hinzufügten) fest. Setzen Sie den Identifier auch auf cell (die gleiche Zeichenfolge, die wir für den cellReuseIdentifier im obigen Code verwendet haben.

Null Zeilen für Label

Setzen Sie die Anzahl der Zeilen in Ihrem Label auf 0 . Dies bedeutet mehrzeilig und ermöglicht es dem Label, sich basierend auf seinem Inhalt selbst zu skalieren.

Schließen Sie die Ausgänge an

  • Steuern Sie den tableView von der Tabellenansicht im Storyboard auf die Variable " tableView im ViewController Code.
  • myCellLabel Sie dasselbe für das Label in der Zelle Prototype für die Variable myCellLabel in der Klasse MyCustomCell .

Fertig

Sie sollten jetzt in der Lage sein, Ihr Projekt auszuführen und Zellen mit variablen Höhen zu erhalten.

Anmerkungen

  • Dieses Beispiel funktioniert nur für iOS 8 und danach. Wenn Sie iOS 7 weiterhin unterstützen müssen, funktioniert dies nicht für Sie.
  • Ihre eigenen benutzerdefinierten Zellen in Ihren zukünftigen Projekten werden wahrscheinlich mehr als nur ein Label haben. Stellen Sie sicher, dass Sie alles richtig angeheftet haben, damit das automatische Layout die richtige Höhe bestimmen kann. Sie müssen möglicherweise auch vertikalen Kompressionswiderstand und Umarmungen verwenden. Sehen Sie diesen Artikel für mehr darüber.
  • Wenn Sie die Vorder- und Hinterkante (links und rechts) nicht fixieren, müssen Sie möglicherweise auch die preferredMaxLayoutWidth der Beschriftung festlegen, damit sie weiß, wann eine Zeilenumbruch erforderlich ist. Wenn Sie beispielsweise der Beschriftung im Projekt oben eine Beschränkung "Mitte horizontal" hinzugefügt hätten, anstatt die führende und die tableView:cellForRowAtIndexPath , müssten Sie diese Zeile der Methode " tableView:cellForRowAtIndexPath :

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
    

Siehe auch


In my case, the padding was because of the sectionHeader and sectionFooter heights, where storyboard allowed me to change it to minimum 1. So in viewDidLoad method:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

An important enough gotcha I just ran into to post as an answer.

@smileyborg's answer is mostly correct. However, if you have any code in the layoutSubviews method of your custom cell class, for instance setting the preferredMaxLayoutWidth , then it won't be run with this code:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

It confounded me for awhile. Then I realized it's because those are only triggering layoutSubviews on the contentView , not the cell itself.

My working code looks like this:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

Note that if you are creating a new cell, I'm pretty sure you don't need to call setNeedsLayout as it should already be set. In cases where you save a reference to a cell, you should probably call it. Either way it shouldn't hurt anything.

Another tip if you are using cell subclasses where you are setting things like preferredMaxLayoutWidth . As @smileyborg mentions, "your table view cell hasn't yet had its width fixed to the table view's width". This is true, and trouble if you are doing your work in your subclass and not in the view controller. However you can simply set the cell frame at this point using the table width:

For instance in the calculation for height:

self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);

(I happen to cache this particular cell for re-use, but that's irrelevant).


Like I ran into an important enough gotcha that I'm posting this as an answer.

I struggled with @smileyborg's answer for a while. The gotcha that I ran into is if you've defined your prototype cell in IB with additional elements ( UILabels , UIButtons , etc.) in IB when you instantiate the cell with [ [YourTableViewCellClass alloc] init] it will not instantiate all the other elements within that cell unless you've written code to do that. (I had a similar experience with initWithStyle .)

To have the storyboard instantiate all the additional elements obtain your cell with [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"] (Not [tableView dequeueReusableCellWithIdentifier:forIndexPath:] as this'll cause interesting problems.) When you do this all the elements you defined in IB will be instantiated.


Hier ist meine Lösung. Sie müssen der TableView die geschätzte Höhe mitteilen, bevor sie die Ansicht lädt. Sonst wird es sich nicht wie erwartet verhalten können.

- (void)viewWillAppear:(BOOL)animated {
    _messageField.delegate = self;
    _tableView.estimatedRowHeight = 65.0;
    _tableView.rowHeight = UITableViewAutomaticDimension;
}

Let's say you have a cell with a subview, and you want the cell's height to be high enough to encompass the subview + padding.

1) Set the subview's bottom constraint equal to the cell.contentView minus the padding you want. Do not set constraints on the cell or cell.contentView itself.

2) Set either the tableView's rowHeight property or tableView:heightForRowAtIndexPath: to UITableViewAutomaticDimension .

3) Set either the tableView's estimatedRowHeight property or tableView:estimatedHeightForRowAtIndexPath: to a best guess of the height.

Das ist es.


TL; DR: Mag nicht lesen? Gehe direkt zu den Beispielprojekten auf GitHub:

Begriffsbeschreibung

Die ersten beiden Schritte gelten unabhängig davon, für welche iOS-Versionen Sie entwickeln.

1. Einrichten und Hinzufügen von Einschränkungen

UITableViewCell in Ihrer UITableViewCell Unterklasse Constraints hinzu, sodass die Kanten der Subviews der Zelle an die Kanten der contentView der Zelle angeheftet werden (am wichtigsten an die obere UND untere Kante). HINWEIS: Unteransichten nicht an die Zelle selbst anhängen; nur zum contentView der Zelle! Die Inhaltsgröße dieser Untersichten sollte die Höhe der Inhaltsansicht der Tabellenansichtszelle bestimmen, indem sichergestellt wird, dass die Inhaltskomprimierungswiderstände und Inhaltsbeschränkungen in der vertikalen Dimension für jede Unteransicht nicht durch Einschränkungen höherer Priorität überschrieben werden, die Sie hinzugefügt haben. ( Hä? Klick hier. )

Denken Sie daran, dass die Unteransichten der Zelle vertikal mit der Inhaltsansicht der Zelle verbunden sein müssen, damit sie "Druck ausüben" und die Inhaltsansicht erweitern können, um sie anzupassen. In einer Beispielzelle mit einigen Unteransichten sehen Sie hier eine visuelle Darstellung, wie einige (nicht alle!) Ihrer Einschränkungen aussehen müssten:

Sie können sich vorstellen, dass mehr Text zur mehrzeiligen Textbeschriftung in der obigen Beispielzelle hinzugefügt werden muss, damit er vertikal wachsen kann, damit er in die Höhe passt. (Natürlich müssen Sie die Einschränkungen richtig machen, damit dies richtig funktioniert!)

Es ist definitiv der schwierigste und wichtigste Teil , um dynamische Zellenhöhen mit Auto Layout arbeiten zu lassen. Wenn Sie hier einen Fehler machen, könnte das alles daran hindern, zu funktionieren - nehmen Sie sich also Zeit! Ich empfehle, die Einschränkungen in Code zu definieren, weil Sie genau wissen, welche Einschränkungen wo hinzugefügt werden, und es ist viel einfacher zu debuggen, wenn etwas schief geht. Das Hinzufügen von Constraints im Code kann genauso einfach und wesentlich leistungsfähiger sein als der Interface Builder, der Layout-Anker oder eine der fantastischen Open-Source-APIs verwendet, die auf GitHub verfügbar sind.

  • Wenn Sie Einschränkungen im Code hinzufügen, sollten Sie dies einmal innerhalb der updateConstraints Methode Ihrer UITableViewCell-Unterklasse tun. Beachten Sie, dass updateConstraints kann. Um zu vermeiden, dass dieselben Einschränkungen mehr als einmal hinzugefügt werden, müssen Sie den constraint-addierenden Code innerhalb von updateConstraints in eine Überprüfung für eine boolesche Eigenschaft wie didSetupConstraints (die Sie nach Ihnen auf YES setzen) didSetupConstraints Führen Sie den Constraint-Code einmal aus. Wenn Sie jedoch Code haben, der vorhandene Constraints aktualisiert (z. B. die constant für einige Constraints anpassen), platzieren Sie diese in updateConstraints aber außerhalb der Überprüfung für didSetupConstraints damit sie bei jedem didSetupConstraints der Methode ausgeführt werden kann.

2. Ermitteln Sie die eindeutigen Wiederverwendungsidentifizierer für Tabellenansichtszellen

Verwenden Sie für jeden eindeutigen Satz von Integritätsbedingungen in der Zelle einen eindeutigen Zellwiederverwendungsbezeichner. Mit anderen Worten, wenn Ihre Zellen mehr als ein eindeutiges Layout haben, sollte jedes eindeutige Layout eine eigene Wiederverwendungskennung erhalten. (Ein guter Hinweis, dass Sie eine neue Wiederverwendungs-ID verwenden müssen, ist, wenn Ihre Zellenvariante eine andere Anzahl von Untersichten aufweist oder die Untersichten in einer anderen Art angeordnet sind.)

Wenn Sie beispielsweise in jeder Zelle eine E-Mail-Nachricht anzeigen, verfügen Sie möglicherweise über vier eindeutige Layouts: Nachrichten mit nur einem Betreff, Nachrichten mit einem Betreff und einem Text, Nachrichten mit einem Betreff und einem Fotoanhang sowie Nachrichten mit einem Betreff. Körper und Fotoanhang. Jedes Layout hat völlig andere Einschränkungen, die erforderlich sind, um es zu erreichen. Sobald die Zelle initialisiert ist und die Beschränkungen für einen dieser Zelltypen hinzugefügt sind, sollte die Zelle eine eindeutige Wiederverwendungskennung erhalten, die für diesen Zelltyp spezifisch ist. Das bedeutet, wenn Sie eine Zelle zur Wiederverwendung entfernen, wurden die Einschränkungen bereits hinzugefügt und sind bereit für diesen Zelltyp zu gehen.

Beachten Sie, dass Zellen mit denselben Einschränkungen (Typ) aufgrund von Unterschieden in der Größe des intrinsischen Inhalts immer noch unterschiedliche Höhen haben können! Verwechseln Sie grundsätzlich unterschiedliche Layouts (unterschiedliche Constraints) nicht mit unterschiedlichen berechneten View-Frames (die von identischen Constraints gelöst werden) aufgrund unterschiedlicher Inhaltsgrößen.

  • Fügen Sie keine Zellen mit völlig unterschiedlichen Constraints zu demselben Wiederverwendungspool hinzu (dh verwenden Sie denselben Wiederverwendungsidentifizierer) und versuchen Sie dann, die alten Constraints zu entfernen und neue Constraints nach jeder Dequeue neu zu erstellen. Die interne Auto-Layout-Engine ist nicht dafür ausgelegt, große Änderungen der Einschränkungen zu verarbeiten, und Sie werden massive Leistungsprobleme feststellen.

Für iOS 8 - Self-Sizing-Zellen

3. Aktivieren Sie Zeilenhöhenschätzung

Um Zellen mit selbstanpassender Tabellenansicht zu aktivieren, müssen Sie die rowHeight-Eigenschaft der Tabellenansicht auf UITableViewAutomaticDimension festlegen. Sie müssen der Eigenschaft "geschätzteRowHeight" auch einen Wert zuweisen. Sobald diese beiden Eigenschaften gesetzt sind, berechnet das System mit Auto Layout die tatsächliche Höhe der Zeile

Apple: Arbeiten mit Self-Sizing-Tabellenansichtszellen

Mit iOS 8 hat Apple einen Großteil der Arbeit verinnerlicht, die zuvor von Ihnen vor iOS 8 implementiert werden musste. Damit der Self-Sizing- rowHeight funktionieren kann, müssen Sie zunächst die rowHeight Eigenschaft in der Tabellenansicht auf die Konstante UITableViewAutomaticDimension . Dann müssen Sie die estimatedRowHeight der Zeilenhöhe nur aktivieren, indem Sie die answeredRowHeight-Eigenschaft der Tabelle auf einen Wert ungleich null setzen, z.

self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is

Dadurch erhält die Tabellenansicht eine temporäre Schätzung / einen Platzhalter für die Zeilenhöhen von Zellen, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen gerade auf dem Bildschirm rollen, wird die tatsächliche Zeilenhöhe berechnet. Um die tatsächliche Höhe für jede Zeile zu bestimmen, fragt die Tabellenansicht automatisch jede Zelle nach der Höhe, die ihr contentView auf der bekannten festen Breite der Inhaltsansicht basieren muss (basierend auf der Breite der Tabellenansicht abzüglich zusätzlicher Dinge wie einem Abschnitt) Index- oder Zubehöransicht) und die automatischen Layoutbeschränkungen, die Sie der Inhaltsansicht und den Unteransichten der Zelle hinzugefügt haben. Sobald diese tatsächliche Zellenhöhe bestimmt wurde, wird die alte geschätzte Höhe für die Zeile mit der neuen tatsächlichen Höhe aktualisiert (und alle Anpassungen der contentSize / contentOffset der Tabellenansicht werden nach Bedarf für Sie vorgenommen).

Im Allgemeinen muss die von Ihnen bereitgestellte Schätzung nicht sehr genau sein - sie wird nur zur korrekten Größenanpassung des Bildlaufanzeigers in der Tabellenansicht verwendet, und in der Tabellenansicht wird der Bildlaufanzeiger für falsche Schätzungen genauso angepasst wie für Sie Scroll-Zellen auf dem Bildschirm. Sie sollten die Eigenschaft estimatedRowHeight in der Tabellenansicht (in viewDidLoad oder ähnlich) auf einen konstanten Wert setzen, der der "durchschnittlichen" viewDidLoad . Nur wenn Ihre tableView:estimatedHeightForRowAtIndexPath: sind (z. B. um eine Größenordnung unterschiedlich sind) und Sie beim tableView:estimatedHeightForRowAtIndexPath: "springen" bemerken, sollten Sie sich mit der Implementierung von tableView:estimatedHeightForRowAtIndexPath: um die Minimalberechnung für eine genauere Schätzung tableView:estimatedHeightForRowAtIndexPath: jede Reihe.

Für iOS 7-Unterstützung (Implementieren der automatischen Zellengröße selbst)

3. Führen Sie ein Layout aus und erhalten Sie die Zellenhöhe

Instanziieren Sie zuerst eine Offscreen-Instanz einer Tabellenansichtszelle, eine Instanz für jeden Wiederverwendungsbezeichner , die ausschließlich für Höhenberechnungen verwendet wird. (Offscreen bedeutet, dass die Zellreferenz in einer Eigenschaft / einem ivar auf dem View-Controller gespeichert und nie von tableView:cellForRowAtIndexPath: damit die Tabellenansicht tatsächlich auf dem Bildschirm dargestellt wird.) Anschließend muss die Zelle mit dem genauen Inhalt konfiguriert werden (z. B. Text, Bilder usw.), die es behalten würde, wenn es in der Tabellenansicht angezeigt würde.

Fordern Sie dann die Zelle zum sofortigen Layout ihrer Untersichten auf und verwenden Sie dann die Methode systemLayoutSizeFittingSize: für die UITableViewCell contentView , um herauszufinden, wie hoch die erforderliche contentView ist. Verwenden Sie UILayoutFittingCompressedSize , um die kleinste erforderliche Größe für den gesamten Inhalt der Zelle zu erhalten. Die Höhe kann dann von der Methode tableView:heightForRowAtIndexPath: delegate zurückgegeben werden.

4. Verwenden Sie geschätzte Zeilenhöhen

Wenn Ihre Tabellenansicht mehr als ein paar Dutzend Zeilen enthält, werden Sie feststellen, dass die automatische Layout- tableView:heightForRowAtIndexPath: den Hauptthread beim ersten Laden der Tabellenansicht tableView:heightForRowAtIndexPath: , da tableView:heightForRowAtIndexPath: für jede einzelne Zeile aufgerufen wird beim ersten Laden (um die Größe des Scrollindikators zu berechnen).

Ab iOS 7 können (und sollten Sie unbedingt) die Eigenschaft estimatedRowHeight in der Tabellenansicht verwenden. Dadurch erhält die Tabellenansicht eine temporäre Schätzung / einen Platzhalter für die Zeilenhöhen von Zellen, die noch nicht auf dem Bildschirm angezeigt werden. Wenn diese Zellen gerade auf dem Bildschirm tableView:heightForRowAtIndexPath: , wird die tatsächliche tableView:heightForRowAtIndexPath: berechnet (durch Aufrufen von tableView:heightForRowAtIndexPath: und die geschätzte Höhe wird mit der tatsächlichen aktualisiert.

Im Allgemeinen muss die von Ihnen bereitgestellte Schätzung nicht sehr genau sein - sie wird nur zur korrekten Größenanpassung des Bildlaufanzeigers in der Tabellenansicht verwendet, und in der Tabellenansicht wird der Bildlaufanzeiger für falsche Schätzungen genauso angepasst wie für Sie Scroll-Zellen auf dem Bildschirm. Sie sollten die Eigenschaft estimatedRowHeight in der Tabellenansicht (in viewDidLoad oder ähnlich) auf einen konstanten Wert setzen, der der "durchschnittlichen" viewDidLoad . Nur wenn Ihre tableView:estimatedHeightForRowAtIndexPath: sind (z. B. um eine Größenordnung unterschiedlich sind) und Sie beim tableView:estimatedHeightForRowAtIndexPath: "springen" bemerken, sollten Sie sich mit der Implementierung von tableView:estimatedHeightForRowAtIndexPath: um die Minimalberechnung für eine genauere Schätzung tableView:estimatedHeightForRowAtIndexPath: jede Reihe.

5. (Wenn erforderlich) Zeilenhöhen-Caching hinzufügen

Wenn Sie alle oben tableView:heightForRowAtIndexPath: und immer noch feststellen, dass die Leistung beim Ausführen der Constraint-Lösung in tableView:heightForRowAtIndexPath: unannehmbar langsam ist, müssen Sie leider etwas Caching für tableView:heightForRowAtIndexPath: implementieren. (Dies ist der von den Ingenieuren von Apple vorgeschlagene Ansatz.) Die allgemeine Idee ist, dass die Auto-Layout-Engine die Beschränkungen beim ersten Mal löst, dann die berechnete Höhe für diese Zelle zwischenspeichert und den zwischengespeicherten Wert für alle zukünftigen Anforderungen für die Höhe dieser Zelle verwendet. Der Trick besteht natürlich darin, sicherzustellen, dass Sie die zwischengespeicherte Höhe für eine Zelle löschen, wenn etwas passiert, das die Höhe der Zelle ändern könnte - in erster Linie, wenn sich der Inhalt dieser Zelle ändert oder wenn andere wichtige Ereignisse eintreten (wie der Benutzer anpasst) den Schieberegler für den Schriftgrad des dynamischen Typs).

iOS 7 Generic Sample Code (mit vielen saftigen Kommentaren)

- (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 multi-line 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 contentView
    CGFloat 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;
    }
}

Beispielprojekte

Diese Projekte sind voll funktionsfähige Beispiele für Tabellenansichten mit variablen Zeilenhöhen aufgrund von Tabellenansichtszellen, die dynamischen Inhalt in UILabels enthalten.

Xamarin (C # /. NET)

Wenn Sie Xamarin verwenden, sehen Sie sich dieses Beispielprojekt an, das von @KentBoogaart zusammengestellt wurde.


Another "solution": skip all this frustration and use a UIScrollView instead to get a result that looks and feels identical to UITableView.

That was the painful "solution" for me, after having put in literally 20+ very frustrating hours total trying to build something like what smileyborg suggested and failing over many months and three versions of App Store releases.

My take is that if you really need iOS 7 support (for us, it's essential) then the technology is just too brittle and you'll pull your hair out trying. And that UITableView is complete overkill generally unless you're using some of the advanced row editing features and/or really need to support 1000+ "rows" (in our app, it's realistically never more than 20 rows).

The added bonus is that the code gets insanely simple versus all the delegate crap and back and forth that comes with UITableView. It's just one single loop of code in viewOnLoad that looks elegant and is easy to manage.

Here's some tips on how to do it:

1) Using either Storyboard or a nib file, create a ViewController and associated root view.

2) Drag over a UIScrollView onto your root view.

3) Add constraints top, bottom, left and right constraints to the top-level view so the UIScrollView fills the entire root view.

4) Add a UIView inside the UIScrollView and call it "container". Add top, bottom, left and right constraints to the UIScrollView (its parent). KEY TRICK: Also add a "Equal widths" constraints to link the UIScrollView and UIView.

You will get an error "scroll view has ambiguous scrollable content height" and that your container UIView should have a height of 0 pixels. Neither error seems to matter when the app is running.

5) Create nib files and controllers for each of your "cells". Use UIView not UITableViewCell.

5) In your root ViewController, you essentially add all the "rows" to the container UIView and programmatically add constraints linking their left and right edges to the container view, their top edges to either the container view top (for the first item) or the previous cell. Then link the final cell to the container bottom.

For us, each "row" is in a nib file. So the code looks something like this:

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!)
    }
}

And here's the code for UITools.addViewToTop:

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))
    }
}

The only "gotcha" I've found with this approach so far is that UITableView has a nice feature of "floating" section headers at the top of the view as you scroll. The above solution won't do that unless you add more programming but for our particular case this feature wasn't 100% essential and nobody noticed when it went away.

If you want dividers between your cells, just add a 1 pixel high UIView at the bottom of your custom "cell" that looks like a divider.

Be sure to turn on "bounces" and "bounce vertically" for the refresh control to work and so it seems more like a tableview.

TableView shows some empty rows and dividers under your content, if it doesn't fill the full screen where as this solution doesn't. But personally, I prefer if those empty rows weren't there anyway - with variable cell height it always looked "buggy" to me anyway to have the empty rows in there.

Here's hoping some other programmer reads my post BEFORE wasting 20+ hours trying to figure it out with Table View in their own app. :)


With regard to the accepted answer by @smileyborg, I have found

[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]

to be unreliable in some cases where constraints are ambiguous. Better to force the layout engine to calculate the height in one direction, by using the helper category on UIView below:

-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
    [self setNeedsLayout];
    [self layoutIfNeeded];
    CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGFloat h = size.height;
    return h;
}

Where w: is the width of the tableview


Dies kann durch Festlegen (oder Zurückgeben) der estimatedSectionHeaderHeight SectionHeaderHeight in der Tabellenansicht erreicht werden.

Wenn Ihre Abschnittsüberschrift nach dem Festlegen von answeredSectionHeaderHeight Ihre Zellen überlappt , stellen Sie sicher, dass Sie auch eine estimatedRowHeight .

(Ich füge diese Antwort hinzu, weil der zweite Absatz eine Antwort auf ein Problem enthält, das nach dem Durchlesen aller Kommentare gefunden werden kann, die einige möglicherweise übersehen haben.)





ios uitableview autolayout nsautolayout row-height