Resize Superview After Subviews Change Dynamically Using Autolayout

resize superview after subviews change dynamically using autolayout

If you're using Auto Layout, here's what you need to do:

  1. Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.

  2. Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.

  3. Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.

That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)

Resize superview after changing height of subview - WITHOUT AUTO LAYOUT

self.layoutSubviews() will not work in this case. It will work only when used auto layouts. As you are managing frames manually, you should set frames for all superviews and subviews whenever you changed frame of one view. Its a tricky thing.

Try to implement Auto layouts. Auto layouts will manage these kind of things easily.

Can subviews resize their superview automatically with auto layout?

Yes, you can do that, and it can be done without any code or subclassing. For instance, if you have a label embedded in a view (with numberOfLines = 0), and it has constraints to to all the sides of the superview, as well as a width constraint, the label, and its superview will expand vertically if you put a large string in the label. The superview should still need to have some constraints, like one to the top and left side of its superview, but no size constraints.

Resize superview while adding subviews dynamically with autolayout

You can outlet height constraint of the view, and then set value accordingly to elements.

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

How to resize the superview to conform to the subviews constraints

You have to initialize the view with a frame, but when setting translatesAutoresizingMaskIntoConstraints to false after initializing, the frame passed in becomes irrelevant. Therefore, you can do what @BallpointBen recommended in the comments and pass in .zero:

let newCustomView = CustomView(frame: .zero)
newCustomView.translatesAutoresizingMaskIntoConstraints = false

// auto layout constraints should now determine height

Resize superview and all superview's siblings using autolayout

I managed to figure this out.

For anyone reading this having the same issue, the way I did it was to write my own subclass of UITextView giving it a height constraint and a few delegate methods.

When changing the height of the text view, simply fire the delegate method (e.g. textViewDidChangeHeight:). Then, in the delegate call back implementation, I just called sizeToFit on the view.

How to adjust super view 's height base on subview's size in XIB?

Using Auto Layout, here's what you need to do:

  1. Make sure you aren't adding fixed width and/or height constraints to any of your subviews (depending on which dimension(s) you want to dynamically size). The idea is to let the intrinsic content size of each subview determine the subview's height. UILabels come with 4 automatic implicit constraints which will (with less than Required priority) attempt to keep the label's frame at the exact size required to fit all the text inside.

  2. Make sure that the edges of each label are connected rigidly (with Required priority constraints) to the edges of each other and their superview. You want to make sure that if you imagine one of the labels growing in size, this would force the other labels to make room for it and most importantly force the superview to expand as well.

  3. Only add constraints to the superview to set its position, not size (at least, not for the dimension(s) you want it to size dynamically). Remember that if you set the internal constraints up correctly, its size will be determined by the sizes of all the subviews, since its edges are connected to theirs in some fashion.

  4. Make sure you change to view's translatesAutoresizingMaskIntoConstraints property value to NO before applying any constraints.

That's it. You don't need to call sizeToFit or systemLayoutSizeFittingSize: to get this to work, just load your views and set the text and that should be it. The system layout engine will do the calculations for you to solve your constraints. (If anything, you might need to call setNeedsLayout on the superview...but this shouldn't be required.)

You can check the original answer here



Related Topics



Leave a reply



Submit