如何调整superview的大小以适应autolayout的所有子视图?

时间:2013-08-08 04:39:57

标签: ios uitableview autolayout

我对自动布局的理解是它采用超视图的大小,并以约束和内在大小为基础计算子视图的位置。

有没有办法扭转这个过程?我想在约束和内在大小的基础上调整superview的大小。实现这一目标的最简单方法是什么?

我有在Xcode中设计的视图,我将其用作UITableView的标题。该视图包括标签和按钮。标签的大小因数据而异。根据约束条件,标签成功按下按钮,或者如果按钮和超视图底部之间存在约束,则标签将被压缩。

我发现了一些类似的问题,但他们没有很好的答案。

4 个答案:

答案 0 :(得分:148)

要使用的正确API是UIView systemLayoutSizeFittingSize:,通过UILayoutFittingCompressedSizeUILayoutFittingExpandedSize

对于使用自动布局的普通UIView,只要您的约束正确,这应该可以正常工作。如果您想在UITableViewCell上使用它(例如确定行高),那么您应该针对您的单元格contentView调用它并获取高度。

如果您的视图中有一个或多个UILabel是多行的,则存在进一步的考虑因素。对于这些,我们必须正确设置preferredMaxLayoutWidth属性,以便标签提供正确的intrinsicContentSize,这将在systemLayoutSizeFittingSize's计算中使用。

编辑:根据要求,为表格视图单元格添加高度计算示例

使用autolayout进行表格单元格高度计算并不是非常有效,但它确实很方便,特别是如果您的单元格具有复杂的布局。

如上所述,如果您使用多行UILabel,则必须将preferredMaxLayoutWidth同步到标签宽度。我使用自定义UILabel子类来执行此操作:

@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

这是一个人为的UITableViewController子类,演示了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

一个简单的自定义单元格:

#import "TSTableViewCell.h"
#import "TSLabel.h"

@implementation TSTableViewCell
{
    IBOutlet TSLabel* _label;
}

- (void) setText: (NSString *) text
{
    _label.text = text;
}

@end

并且,这是故事板中定义的约束的图片。请注意,标签上没有高度/宽度限制 - 这些是从标签的intrinsicContentSize推断出来的:

enter image description here

答案 1 :(得分:29)

Eric Baker的评论让我想到了的核心思想,为了让视图的大小由放在其中的内容决定,然后放在其中的内容必须与包含视图有明确的关系为了动态地驱动它的高度(或宽度)。 “添加子视图”不会像您假设的那样创建此关系。您必须选择哪个子视图将驱动容器的高度和/或宽度...最常见的是放置在整个UI右下角的UI元素。这里有一些代码和内联注释来说明这一点。

请注意,这对于使用滚动视图的人来说可能特别有价值,因为围绕单个内容视图进行设计是很常见的,该视图根据您放入的内容动态地确定其大小(并将其传递给滚动视图)。祝你好运,希望这有助于那里的人。

//
//  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

答案 2 :(得分:3)

您可以通过创建约束并通过界面构建​​器

连接它来完成此操作

参见说明: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;

将此Constraint插座与子视图Constraint连接或连接超级视图Constraint并根据您的要求进行设置

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

我希望这能澄清它。

答案 3 :(得分:-1)

这可以针对较大subview内的正常UIView进行,但不会自动对headerViews起作用。 headerView的高度取决于tableView:heightForHeaderInSection:返回的内容,因此您必须根据height的{​​{1}}空间来计算height UILabel以及您需要的任何UIButton。你需要做这样的事情:

padding

此处-(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; } 是您希望填充headerString的任何字符串,而281号码是UILabel的{​​{1}}(如width中的设置)