ios - استخدام Auto Layout في UITableView للتنسيقات الحيوية للخلية وارتفاع الصف المتغير

autolayout nsautolayout row-height (21)

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

كيف يمكنك استخدام Auto Layout في UITableViewCell s في عرض جدول للسماح لمحتوى كل خلية وعينات فرعية بتحديد ارتفاع الصف (نفسه / تلقائيًا) ، مع الحفاظ على أداء التمرير السلس؟

Dynamic Table View Cell Height and Auto Layout

A good way to solve the problem with storyboard Auto Layout:

- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
  static RWImageCell *sizingCell = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];

  [sizingCell setNeedsLayout];
  [sizingCell layoutIfNeeded];

  CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
  return size.height;

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() {


        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) {
                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()

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)

        //Set left and right constraints so fills full horz width:

            item: child,
            attribute: NSLayoutAttribute.Leading,
            relatedBy: NSLayoutRelation.Equal,
            toItem: container,
            attribute: NSLayoutAttribute.Left,
            multiplier: 1,
            constant: 0))

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

    [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

yet another iOs7+iOs8 solution in Swift

var cell2height:CGFloat=44

override func viewDidLoad() {
    theTable.rowHeight = UITableViewAutomaticDimension
    theTable.estimatedRowHeight = 44.0;

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell =  tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
    return cell

func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    if #available(iOS 8.0, *) {
        return UITableViewAutomaticDimension
    } else {
        return cell2height

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?

أنا ملفوفة الحل @ smileyborg iOS7 في فئة

قررت أن التفاف هذا الحل ذكي من قبل @ Smileyborg في فئة UICollectionViewCell+AutoLayoutDynamicHeightCalculation .

تقوم الفئة أيضًا بتصحيح المشكلات الموضحة في إجابة wildmonkey @ (تحميل خلية من nib و systemLayoutSizeFittingSize: CGRectZero )

لا يأخذ في الاعتبار أي تخزين مؤقت ولكنه يناسب احتياجاتي الآن. لا تتردد في النسخ واللصق والاختراق في ذلك.

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;


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

#pragma mark Height

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

- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width


    [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;



مثال على الاستخدام:

- (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);

لحسن الحظ لن يكون لدينا للقيام بهذا الجاز في iOS8 ، ولكن هناك الآن!

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

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.

هذا هو.

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)

private func setUpViews() {

    let marginGuide = contentView.layoutMarginsGuide

        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.


  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

بالنسبة لـ IOS8 فهو بسيط للغاية:

override func viewDidLoad() {  

    self.tableView.estimatedRowHeight = 80
    self.tableView.rowHeight = UITableViewAutomaticDimension


func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension

ولكن بالنسبة إلى IOS7 ، يكون المفتاح هو حساب الارتفاع بعد autolayout ،

func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
    let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
    return height


  • إذا كانت هناك تسميات متعددة numberOfLines ، فلا تنس تعيين numberOfLines على 0 .

  • لا تنس label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

رمز المثال الكامل موجود here .

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

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.

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

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
        [self.contentView addConstraint:contentViewConstraint];

Mixing @smileyborg answer with this should works.

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)

TL ؛ DR: لا أحب القراءة؟ الانتقال مباشرةً إلى نموذج المشاريع على GitHub:

وصف مفاهيمي

الخطوات الأولى والثانية أدناه قابلة للتطبيق بغض النظر عن إصدارات iOS التي تقوم بتطويرها.

1. إعداد وإضافة القيود

في الفئة الفرعية UITableViewCell ، أضف القيود بحيث يتم تعليق حواف الخلية الخاصة بها على حواف المحتوى الخاص بالخلية (الأكثر أهمية للحواف العلوية والسفلية). ملاحظة: لا تقم بتثبيت العروض الفرعية للخلية نفسها ؛ فقط إلى contentView الخلية! دع حجم المحتوى الأساسي لهذه العروض الفرعية يؤدي إلى ارتفاع عرض محتوى خلية عرض الجدول عن طريق التأكد من أن مقاومة ضغط المحتوى وقيود محتوى المحتوى في البعد الرأسي لكل عرض فرعي لا يتم تجاوزها بسبب القيود ذات الأولوية الأعلى التي أضفتها. ( هاه؟ انقر هنا. )

تذكر أن الفكرة هي أن تكون المقطوعات الفرعية للخلية متصلة بشكل عمودي مع عرض محتوى الخلية بحيث يمكنها "ممارسة الضغط" وتوسيع نطاق عرض المحتوى لتلائمها. باستخدام مثال الخلية مع بعض المقارنات الفرعية ، إليك مثالاً توضيحياً لما يجب أن تبدو عليه بعض القيود (وليس كلها!) :

يمكنك أن تتخيل أنه كلما تمت إضافة المزيد من النص إلى ملصق النص متعدد الأسطر في الخلية المثالية أعلاه ، سوف تحتاج إلى أن تنمو رأسياً لتلائم النص ، مما سيجبر الخلية على النمو في الارتفاع بشكل فعال. (بالطبع ، تحتاج إلى الحصول على القيود الصحيحة لكي يعمل هذا بشكل صحيح!)

من المؤكد أن الحصول على القيود الصحيحة هو الجزء الأصعب والأكثر أهمية في الحصول على ارتفاعات الخلايا الديناميكية التي تعمل مع Auto Layout. إذا ارتكبت خطأ هنا ، فقد تمنع أي شيء آخر من العمل - لذا خذ وقتك! أوصي بإعداد القيود الخاصة بك في التعليمات البرمجية لأنك تعرف بالضبط ما هي القيود التي يتم إضافتها إلى المكان ، ومن الأسهل كثيرًا تصحيح الأخطاء عندما تسوء الأمور. يمكن أن تكون إضافة قيود في التعليمات البرمجية بنفس السهولة والأقوى بكثير من Interface Builder باستخدام المخططات التخطيطية ، أو أحد واجهات برمجة التطبيقات المفتوحة المصدر الرائعة المتوفرة على GitHub.

  • إذا كنت تضيف قيودًا في التعليمة البرمجية ، فيجب عليك القيام بذلك مرة واحدة من خلال أسلوب updateConstraints الخاص updateConstraints الخاصة بك. لاحظ أنه قد يتم استدعاء updateConstraints أكثر من مرة ، وذلك لتجنب إضافة نفس القيود أكثر من مرة واحدة ، تأكد من التفاف الشفرة التي تقوم updateConstraints ضمن updateConstraints في شيك لخاصية boolean مثل didSetupConstraints (الذي قمت بتعيينه إلى YES بعد تشغيل التعليمات البرمجية الخاصة بك القيد-إضافة مرة واحدة). من ناحية أخرى ، إذا كان لديك رمز يقوم بتحديث القيود الموجودة (مثل ضبط الخاصية constant على بعض القيود) ، ضع هذا في updateConstraints ولكن خارج التحقق من وجود didSetupConstraints بحيث يمكن تشغيله في كل مرة يتم استدعاء الأسلوب.

2. تحديد فريدة معرفات إعادة تعريف خلية عرض الجدول

لكل مجموعة فريدة من القيود في الخلية ، استخدم معرف فريد لإعادة استخدام الخلية. بمعنى آخر ، إذا كانت لخلاياك أكثر من تخطيط فريد واحد ، فيجب أن يتلقى كل تخطيط فريد معرف إعادة الاستخدام الخاص به. (هناك تلميح جيد أنك تحتاج إلى استخدام معرّف جديد لإعادة الاستخدام عندما يكون متغير الخلية الخاص بك يحتوي على عدد مختلف من المقابلات الفرعية ، أو يتم ترتيب العرضين الفرعيين بطريقة مختلفة.)

على سبيل المثال ، إذا كنت تعرض رسالة بريد إلكتروني في كل خلية ، فقد يكون لديك 4 تخطيطات فريدة: رسائل ذات موضوع واحد فقط ، ورسائل مع موضوع ونص ، ورسائل مع موضوع ومرفقات صور ، ورسائل مع موضوع ، الجسم ، ومرفق الصورة. يحتوي كل تخطيط على قيود مختلفة تمامًا مطلوبة لتحقيق ذلك ، وبمجرد أن تتم تهيئة الخلية وإضافة القيود لأحد هذه الأنواع من الخلايا ، يجب أن تحصل الخلية على معرف إعادة استخدام فريد خاص بنوع الخلية هذا. هذا يعني أنه عندما تقوم بتخصيص إحدى الخلايا لإعادة الاستخدام ، فقد تمت إضافة القيود بالفعل وأصبحت جاهزة للانتقال إلى ذلك النوع من الخلايا.

لاحظ أنه نظرًا للاختلافات في حجم المحتوى الداخلي ، فإن الخلايا التي لها نفس القيود (النوع) قد لا تزال لديها ارتفاعات متفاوتة! لا تخلط بين التخطيطات المختلفة جذريًا (قيود مختلفة) مع إطارات عرض محسوبة مختلفة (تم حلها من قيود مماثلة) بسبب أحجام مختلفة من المحتوى.

  • لا تقم بإضافة خلايا بمجموعات مختلفة تمامًا من القيود إلى نفس مجموعة إعادة الاستخدام (أي استخدام نفس معرف إعادة الاستخدام) ثم محاولة إزالة القيود القديمة ووضع قيود جديدة من نقطة الصفر بعد كل ممر. المحرك الداخلي Auto Layout غير مصمم للتعامل مع التغييرات الكبيرة في القيود ، وستظهر لك مشكلات هائلة في الأداء.

لنظام التشغيل iOS 8 - خلايا التحجيم الذاتي

3. تمكين تقدير ارتفاع الصف

لتمكين خلايا عرض الجدول ذاتية التحجيم ، يجب عليك تعيين خاصية rowHeight الخاصة بعرض الجدول إلى UITableViewAutomaticDimension. يجب أيضًا تعيين قيمة للقيمة المقدّرة RowHeight. بمجرد تعيين كل من هذه الخصائص ، يستخدم النظام التخطيط التلقائي لحساب الارتفاع الفعلي للصف

Apple: العمل مع خلايا عرض الجدول ذاتية التحجيم

مع نظام التشغيل iOS 8 ، استوعبت شركة Apple الكثير من العمل الذي كان يتعين عليك تنفيذه مسبقًا قبل استخدام iOS 8. لكي تسمح آلية الخلية ذاتية التحجيم بالعمل ، يجب أولاً تعيين خاصية rowHeight في عرض الجدول إلى ثابت UITableViewAutomaticDimension . بعد ذلك ، تحتاج ببساطة إلى تمكين تقدير ارتفاع الصف عن طريق تعيين خاصية rowHeight الخاصة بعرض الجدول إلى قيمة غير صفرية ، على سبيل المثال:

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

ما يفعله هذا هو توفير عرض الجدول مع تقدير مؤقت / عنصر نائب لارتفاع الصفوف للخلايا التي لم تظهر بعد على الشاشة. بعد ذلك ، عندما تكون هذه الخلايا على وشك التمرير على الشاشة ، سيتم حساب ارتفاع الصف الفعلي. لتحديد الارتفاع الفعلي لكل صف ، يسألك عرض الجدول تلقائيًا كل خلية عن الارتفاع الذي يحتاجه contentView الخاص بها استنادًا إلى العرض الثابت المعروف لمحتوى العرض (والذي يعتمد على عرض عرض الجدول ، مطروحًا منه أي أشياء إضافية مثل قسم عرض الفهرس أو الملحق) وقيود التخطيط التلقائي التي أضفتها إلى عرض محتوى الخلية والمراتب الفرعية. بمجرد تحديد هذا الارتفاع الفعلي للخلية ، يتم تحديث الارتفاع التقديري القديم للصف بالارتفاع الفعلي الجديد (وأي تعديلات على محتوى عرض الجدول يتم تحديد الحجم / المحتوى ، يتم إجراء الحذف حسب الحاجة).

بشكل عام ، لا يلزم أن تكون التقديرات التي قمت بتقديمها دقيقة للغاية - يتم استخدامها فقط لحجم مؤشر التمرير بشكل صحيح في عرض الجدول ، ويؤدي عرض الجدول وظيفة جيدة في ضبط مؤشر التمرير لتقديرات غير صحيحة كما تفعل انتقل الخلايا على الشاشة. يجب عليك تعيين الخاصية estimatedRowHeight viewDidLoad عرض الجدول (في viewDidLoad أو ما شابه) إلى قيمة ثابتة هي ارتفاع الصف "متوسط". فقط إذا كانت ارتفاعات صفك لها تباين شديد (على سبيل المثال تختلف حسب ترتيب الحجم) وتلاحظ مؤشر التمرير "القفز" أثناء التمرير يجب أن تزعج تنفيذ tableView:estimatedHeightForRowAtIndexPath: للقيام بالحد الأدنى من الحسابات المطلوبة لإرجاع تقدير أكثر دقة كل صف.

لدعم iOS 7 (تطبيق التحجيم التلقائي للخلية بنفسك)

3. القيام بتمرير تخطيط والحصول على ارتفاع الخلية

أولاً ، قم بإنشاء مثيل خارج الشاشة لخلية عرض الجدول ، ومثيل واحد لكل معرف إعادة استخدام ، والذي يتم استخدامه بدقة لحسابات الارتفاع. (خارج الشاشة بمعنى أنه يتم تخزين مرجع الخلية في خاصية / ivar على وحدة تحكم العرض ولا يتم إرجاعه من tableView:cellForRowAtIndexPath: لعرض الجدول فعليًا على الشاشة). بعد ذلك ، يجب تكوين الخلية بالمحتوى الدقيق (مثل النص ، الصور ، وما إلى ذلك) التي من شأنها أن تعقد إذا كان ليتم عرضها في عرض الجدول.

ثم اجبر الخلية على تخطيط مشاهدتها فورًا ، ثم استخدم systemLayoutSizeFittingSize: method على contentView لمعرفة الارتفاع المطلوب للخلية. استخدم UILayoutFittingCompressedSize للحصول على أصغر حجم مطلوب لتناسب جميع محتويات الخلية. يمكن بعد ذلك إرجاع الارتفاع من tableView:heightForRowAtIndexPath: أسلوب المفوض.

4. استخدم تقديرات مرتفعات الصف

إذا كان عرض الجدول الخاص بك يحتوي على أكثر من بضع عشرات من الصفوف ، فستجد أن حل القيد التلقائي tableView:heightForRowAtIndexPath: بسرعة مؤشر الترابط الرئيسي عند تحميل عرض الجدول للمرة الأولى ، مثل tableView:heightForRowAtIndexPath: يسمى كل صف عند التحميل الأول (لحساب حجم مؤشر التمرير).

اعتبارًا من نظام التشغيل iOS 7 ، يمكنك (ويجب عليك تمامًا) استخدام الخاصية estimatedRowHeight RowHeight في عرض الجدول. ما يفعله هذا هو توفير عرض الجدول مع تقدير مؤقت / عنصر نائب لارتفاع الصفوف للخلايا التي لم تظهر بعد على الشاشة. بعد ذلك ، عندما تكون هذه الخلايا على وشك التمرير على الشاشة ، سيتم حساب ارتفاع الصف الفعلي (عن طريق استدعاء tableView:heightForRowAtIndexPath: ، ويتم تحديث الارتفاع المقدر tableView:heightForRowAtIndexPath: الفعلية.

بشكل عام ، لا يلزم أن تكون التقديرات التي قمت بتقديمها دقيقة للغاية - يتم استخدامها فقط لحجم مؤشر التمرير بشكل صحيح في عرض الجدول ، ويؤدي عرض الجدول وظيفة جيدة في ضبط مؤشر التمرير لتقديرات غير صحيحة كما تفعل انتقل الخلايا على الشاشة. يجب عليك تعيين الخاصية estimatedRowHeight viewDidLoad عرض الجدول (في viewDidLoad أو ما شابه) إلى قيمة ثابتة هي ارتفاع الصف "متوسط". فقط إذا كانت ارتفاعات صفك لها تباين شديد (على سبيل المثال تختلف حسب ترتيب الحجم) وتلاحظ مؤشر التمرير "القفز" أثناء التمرير يجب أن تزعج تنفيذ tableView:estimatedHeightForRowAtIndexPath: للقيام بالحد الأدنى من الحسابات المطلوبة لإرجاع تقدير أكثر دقة كل صف.

5. (إذا لزم الأمر) إضافة صف التخزين المؤقت

إذا كنت قد فعلت كل ما سبق ولا تزال تجد أن الأداء بطيء بشكل غير مقبول عند إجراء حل القيد في tableView:heightForRowAtIndexPath: ، ستحتاج للأسف إلى تنفيذ بعض التخزين المؤقت لارتفاعات الخلية. (هذا هو النهج المقترح من قبل مهندسي شركة Apple.) الفكرة العامة هي السماح لمحرك Auto Layout بحل القيود في المرة الأولى ، ثم تخزين الارتفاع المحسوب لهذه الخلية واستخدام القيمة المخزنة مؤقتًا لكافة الطلبات المستقبلية لارتفاع تلك الخلية. الخدعة بالطبع هي التأكد من مسح الارتفاع المخبأ لخلية عندما يحدث أي شيء يمكن أن يتسبب في تغيير ارتفاع الخلية - في المقام الأول ، سيكون ذلك عندما يتغير محتوى الخلية أو عندما تحدث أحداث مهمة أخرى (مثل ضبط المستخدم شريط التمرير حجم النص الديناميكي).

iOS 7 Generic Sample Code (مع الكثير من التعليقات المثيرة)

- (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;

مشاريع عينة

هذه المشاريع هي أمثلة تعمل بشكل كامل من وجهات النظر الجدول مع ارتفاع الصف المتغير بسبب خلايا عرض الجدول التي تحتوي على محتوى ديناميكي في UILabels.

Xamarin (C # /. NET)

إذا كنت تستخدم Xamarin ، فاطلع على نموذج المشروع هذا الذي وضعته معًا @KentBoogaart .

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.

هذا هو منفذ لإجابة @ rob_mayoff ممتازة لمقاربة مرتكزة على التعليمات البرمجية ، وذلك باستخدام كائنات NSLayoutAnchor ونقلها إلى Xamarin. بالنسبة لي ، NSLayoutAnchor والفصول ذات الصلة برنامج AutoLayout أسهل بكثير:

public class ContentView : UIView
        public ContentView (UIColor fillColor)
            BackgroundColor = fillColor;

public class MyController : UIViewController 
    public override void ViewDidLoad ()
        base.ViewDidLoad ();

        //Starting point:
        var view = new ContentView (UIColor.White);

        blueView = new ContentView (UIColor.FromRGB (166, 200, 255));
        view.AddSubview (blueView);

        lightGreenView = new ContentView (UIColor.FromRGB (200, 255, 220));
        lightGreenView.Frame = new CGRect (20, 40, 200, 60);

        view.AddSubview (lightGreenView);

        pinkView = new ContentView (UIColor.FromRGB (255, 204, 240));
        view.AddSubview (pinkView);

        greenView = new ContentView (UIColor.Green);
        greenView.Frame = new CGRect (80, 20, 40, 200);
        pinkView.AddSubview (greenView);

        //Now start doing in code the things that @rob_mayoff did in IB

        //Make the blue view size up to its parent, but half the height
        blueView.TranslatesAutoresizingMaskIntoConstraints = false;
        var blueConstraints = new []
            blueView.HeightAnchor.ConstraintEqualTo(view.LayoutMarginsGuide.HeightAnchor, (nfloat) 0.5)
        NSLayoutConstraint.ActivateConstraints (blueConstraints);

        //Make the pink view same size as blue view, and linked to bottom of blue view
        pinkView.TranslatesAutoresizingMaskIntoConstraints = false;
        var pinkConstraints = new []
        NSLayoutConstraint.ActivateConstraints (pinkConstraints);

        //From here, address the aspect-fitting challenge:

        lightGreenView.TranslatesAutoresizingMaskIntoConstraints = false;
        //These are the must-fulfill constraints: 
        var lightGreenConstraints = new []
            //Aspect ratio of 1 : 5
            NSLayoutConstraint.Create(lightGreenView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, lightGreenView, NSLayoutAttribute.Width, (nfloat) 0.20, 0),
            //Cannot be larger than parent's width or height
            //Center in parent
        foreach (var c in lightGreenConstraints) 
            c.Priority = 1000;
        NSLayoutConstraint.ActivateConstraints (lightGreenConstraints);

        //Low priority constraint to attempt to fill parent as much as possible (but lower priority than previous)
        var lightGreenLowPriorityConstraints = new []
        //Lower priority
        foreach (var c in lightGreenLowPriorityConstraints) 
            c.Priority = 750;

        NSLayoutConstraint.ActivateConstraints (lightGreenLowPriorityConstraints);

        //Aspect-fit on the green view now
        greenView.TranslatesAutoresizingMaskIntoConstraints = false;
        var greenConstraints = new []
            //Aspect ratio of 5:1
            NSLayoutConstraint.Create(greenView, NSLayoutAttribute.Height, NSLayoutRelation.Equal, greenView, NSLayoutAttribute.Width, (nfloat) 5.0, 0),
            //Cannot be larger than parent's width or height
            //Center in parent
        //Must fulfill
        foreach (var c in greenConstraints) 
            c.Priority = 1000;
        NSLayoutConstraint.ActivateConstraints (greenConstraints);

        //Low priority constraint to attempt to fill parent as much as possible (but lower priority than previous)
        var greenLowPriorityConstraints = new []
        //Lower-priority than above
        foreach (var c in greenLowPriorityConstraints) 
            c.Priority = 750;

        NSLayoutConstraint.ActivateConstraints (greenLowPriorityConstraints);

        this.View = view;

        view.LayoutIfNeeded ();

ios uitableview autolayout nsautolayout row-height