How to resize superview to fit all subviews with autolayout?

My understanding of autolayout is that it takes the size of superview and base on constrains and intrinsic sizes it calculates positions of subviews.

Is there a way to reverse this process? I want to resize superview on the base of constrains and intrinsic sizes. What is the simplest way of achieving this?

  • iOS program to use multiple UITableView in a single UIViewController
  • How to render stretched text in iOS?
  • Objective-C Category and new iVar
  • IOS - How to hide a view by touching anywhere outside of it
  • Xcode 7 with iOS 9 device Error: device unavailable (Could not find a developer disk image)
  • Cannot convert the value of type '' to expected argument type 'String' : while appending arrays to get data from Fireabse in table view cell
  • I have view designed in Xcode which I use as a header for UITableView. This view includes a label and a button. Size of the label differs depending on data. Depending on constrains the label successfully pushes the button down or if there is a constrain between the button and bottom of superview the label is compressed.

    I have found a few similar questions but they don’t have good and easy answers.

    4 Solutions Collect From Internet About “How to resize superview to fit all subviews with autolayout?”

    The correct API to use is UIView systemLayoutSizeFittingSize:, passing either UILayoutFittingCompressedSize or UILayoutFittingExpandedSize.

    For a normal UIView using autolayout this should just work as long as your constraints are correct. If you want to use it on a UITableViewCell (to determine row height for example) then you should call it against your cell contentView and grab the height.

    Further considerations exist if you have one or more UILabel’s in your view that are multiline. For these it is imperitive that the preferredMaxLayoutWidth property be set correctly such that the label provides a correct intrinsicContentSize, which will be used in systemLayoutSizeFittingSize's calculation.

    EDIT: by request, adding example of height calculation for a table view cell

    Using autolayout for table-cell height calculation isn’t super efficient but it sure is convenient, especially if you have a cell that has a complex layout.

    As I said above, if you’re using a multiline UILabel it’s imperative to sync the preferredMaxLayoutWidth to the label width. I use a custom UILabel subclass to do this:

    @implementation TSLabel
    
    - (void) layoutSubviews
    {
        [super layoutSubviews];
    
        if ( self.numberOfLines == 0 )
        {
            if ( self.preferredMaxLayoutWidth != self.frame.size.width )
            {
                self.preferredMaxLayoutWidth = self.frame.size.width;
                [self setNeedsUpdateConstraints];
            }
        }
    }
    
    - (CGSize) intrinsicContentSize
    {
        CGSize s = [super intrinsicContentSize];
    
        if ( self.numberOfLines == 0 )
        {
            // found out that sometimes intrinsicContentSize is 1pt too short!
            s.height += 1;
        }
    
        return s;
    }
    
    @end
    

    Here’s a contrived UITableViewController subclass demonstrating heightForRowAtIndexPath:

    #import "TSTableViewController.h"
    #import "TSTableViewCell.h"
    
    @implementation TSTableViewController
    
    - (NSString*) cellText
    {
        return @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
    }
    
    #pragma mark - Table view data source
    
    - (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView
    {
        return 1;
    }
    
    - (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger) section
    {
        return 1;
    }
    
    - (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath
    {
        static TSTableViewCell *sizingCell;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
    
            sizingCell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell"];
        });
    
        // configure the cell
        sizingCell.text = self.cellText;
    
        // force layout
        [sizingCell setNeedsLayout];
        [sizingCell layoutIfNeeded];
    
        // get the fitting size
        CGSize s = [sizingCell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];
        NSLog( @"fittingSize: %@", NSStringFromCGSize( s ));
    
        return s.height;
    }
    
    - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
    {
        TSTableViewCell *cell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell" ];
    
        cell.text = self.cellText;
    
        return cell;
    }
    
    @end
    

    A simple custom cell:

    #import "TSTableViewCell.h"
    #import "TSLabel.h"
    
    @implementation TSTableViewCell
    {
        IBOutlet TSLabel* _label;
    }
    
    - (void) setText: (NSString *) text
    {
        _label.text = text;
    }
    
    @end
    

    And, here’s a picture of the constraints defined in the Storyboard. Note that there are no height/width constraints on the label – those are inferred from the label’s intrinsicContentSize:

    enter image description here

    Eric Baker’s comment tipped me off to the core idea that in order for a view to have its size be determined by the content placed within it, then the content placed within it must have an explicit relationship with the containing view in order to drive its height (or width) dynamically. “Add subview” does not create this relationship as you might assume. You have to choose which subview is going to drive the height and/or width of the container… most commonly whatever UI element you have placed in the lower right hand corner of your overall UI. Here’s some code and inline comments to illustrate the point.

    Note, this may be of particular value to those working with scroll views since it’s common to design around a single content view that determines its size (and communicates this to the scroll view) dynamically based on whatever you put in it. Good luck, hope this helps somebody out there.

    //
    //  ViewController.m
    //  AutoLayoutDynamicVerticalContainerHeight
    //
    
    #import "ViewController.h"
    
    @interface ViewController ()
    @property (strong, nonatomic) UIView *contentView;
    @property (strong, nonatomic) UILabel *myLabel;
    @property (strong, nonatomic) UILabel *myOtherLabel;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        // INVOKE SUPER
        [super viewDidLoad];
    
        // INIT ALL REQUIRED UI ELEMENTS
        self.contentView = [[UIView alloc] init];
        self.myLabel = [[UILabel alloc] init];
        self.myOtherLabel = [[UILabel alloc] init];
        NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_contentView, _myLabel, _myOtherLabel);
    
        // TURN AUTO LAYOUT ON FOR EACH ONE OF THEM
        self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
        self.myLabel.translatesAutoresizingMaskIntoConstraints = NO;
        self.myOtherLabel.translatesAutoresizingMaskIntoConstraints = NO;
    
        // ESTABLISH VIEW HIERARCHY
        [self.view addSubview:self.contentView]; // View adds content view
        [self.contentView addSubview:self.myLabel]; // Content view adds my label (and all other UI... what's added here drives the container height (and width))
        [self.contentView addSubview:self.myOtherLabel];
    
        // LAYOUT
    
        // Layout CONTENT VIEW (Pinned to left, top. Note, it expects to get its vertical height (and horizontal width) dynamically based on whatever is placed within).
        // Note, if you don't want horizontal width to be driven by content, just pin left AND right to superview.
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to left, no horizontal width yet
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to top, no vertical height yet
    
        /* WHATEVER WE ADD NEXT NEEDS TO EXPLICITLY "PUSH OUT ON" THE CONTAINING CONTENT VIEW SO THAT OUR CONTENT DYNAMICALLY DETERMINES THE SIZE OF THE CONTAINING VIEW */
        // ^To me this is what's weird... but okay once you understand...
    
        // Layout MY LABEL (Anchor to upper left with default margin, width and height are dynamic based on text, font, etc (i.e. UILabel has an intrinsicContentSize))
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];
    
        // Layout MY OTHER LABEL (Anchored by vertical space to the sibling label that comes before it)
        // Note, this is the view that we are choosing to use to drive the height (and width) of our container...
    
        // The LAST "|" character is KEY, it's what drives the WIDTH of contentView (red color)
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];
    
        // Again, the LAST "|" character is KEY, it's what drives the HEIGHT of contentView (red color)
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_myLabel]-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];
    
        // COLOR VIEWS
        self.view.backgroundColor = [UIColor purpleColor];
        self.contentView.backgroundColor = [UIColor redColor];
        self.myLabel.backgroundColor = [UIColor orangeColor];
        self.myOtherLabel.backgroundColor = [UIColor greenColor];
    
        // CONFIGURE VIEWS
    
        // Configure MY LABEL
        self.myLabel.text = @"HELLO WORLD\nLine 2\nLine 3, yo";
        self.myLabel.numberOfLines = 0; // Let it flow
    
        // Configure MY OTHER LABEL
        self.myOtherLabel.text = @"My OTHER label... This\nis the UI element I'm\narbitrarily choosing\nto drive the width and height\nof the container (the red view)";
        self.myOtherLabel.numberOfLines = 0;
        self.myOtherLabel.font = [UIFont systemFontOfSize:21];
    }
    
    @end
    

    How to resize superview to fit all subviews with autolayout.png

    You can do this by creating a constraint and connecting it via interface builder

    See explanation: Auto_Layout_Constraints_in_Interface_Builder

    raywenderlich beginning-auto-layout

    AutolayoutPG Articles constraint Fundamentals

    @interface ViewController : UIViewController {
        IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
        IBOutlet NSLayoutConstraint *topSpaceConstraint;
    }
    @property (weak, nonatomic) IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
    

    connect this Constraint outlet with your sub views Constraint or connect super views Constraint too and set it according to your requirements like this

     self.leadingSpaceConstraint.constant = 10.0;//whatever you want to assign
    

    I hope this clarifies it.

    This can be done for a normal subview inside a larger UIView, but it doesn’t work automatically for headerViews. The height of a headerView is determined by what’s returned by tableView:heightForHeaderInSection: so you have to calculate the height based on the height of the UILabel plus space for the UIButton and any padding you need. You need to do something like this:

    -(CGFloat)tableView:(UITableView *)tableView 
              heightForHeaderInSection:(NSInteger)section {
        NSString *s = self.headeString[indexPath.section];
        CGSize size = [s sizeWithFont:[UIFont systemFontOfSize:17] 
                    constrainedToSize:CGSizeMake(281, CGFLOAT_MAX)
                        lineBreakMode:NSLineBreakByWordWrapping];
        return size.height + 60;
    }
    

    Here headerString is whatever string you want to populate the UILabel, and the 281 number is the width of the UILabel (as setup in Interface Builder)