ios 如何调整超级视图的大小以适应所有具有自动布局的子视图?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18118021/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 00:39:05  来源:igfitidea点击:

How to resize superview to fit all subviews with autolayout?

iosuitableviewautolayout

提问by DAK

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?

有没有办法扭转这个过程?我想根据约束和内在大小调整超级视图的大小。实现这一目标的最简单方法是什么?

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.

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

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

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

回答by TomSwift

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

要使用的正确 API 是UIView systemLayoutSizeFittingSize:,传递UILayoutFittingCompressedSizeUILayoutFittingExpandedSize

For a normal UIViewusing 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 contentViewand grab the height.

对于正常UIView使用自动布局,只要您的约束是正确的,它就应该起作用。如果你想在一个UITableViewCell(例如确定行高)上使用它,那么你应该在你的单元格上调用它contentView并获取高度。

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

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

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 UILabelit's imperative to sync the preferredMaxLayoutWidthto the label width. I use a custom UILabelsubclass to do this:

正如我上面所说,如果您使用的是多行,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

Here's a contrived UITableViewController subclass demonstrating heightForRowAtIndexPath:

这是一个人为的 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

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:

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

enter image description here

在此处输入图片说明

回答by John Erck

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.

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

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

如何使用 autolayout.png 调整超级视图的大小以适合所有子视图

回答by chandan

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

您可以通过创建约束并通过界面构建​​器连接来做到这一点

See explanation: Auto_Layout_Constraints_in_Interface_Builder

见解释:Auto_Layout_Constraints_in_Interface_Builder

raywenderlich beginning-auto-layout

raywenderlich 开始自动布局

AutolayoutPG Articles constraint Fundamentals

AutolayoutPG 文章约束基础

@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

将此 Constraint 出口与您的子视图 Constraint 或连接超级视图 Constraint 并根据您的要求进行设置,如下所示

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

I hope this clarifies it.

我希望这能澄清它。

回答by rdelmar

This can be done for a normal subviewinside a larger UIView, but it doesn't work automatically for headerViews. The height of a headerViewis determined by what's returned by tableView:heightForHeaderInSection:so you have to calculate the heightbased on the heightof the UILabelplus space for the UIButtonand any paddingyou need. You need to do something like this:

这可以subview在更大UIViewheaderViews. a 的高度headerView由返回的内容决定,tableView:heightForHeaderInSection:因此您必须height根据 a和任何您需要heightUILabel加空间来计算 a 。你需要做这样的事情:UIButtonpadding

-(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 headerStringis whatever string you want to populate the UILabel, and the 281 number is the widthof the UILabel(as setup in Interface Builder)

headerString是您要填充 的任何字符串UILabel,而 281 数字是widthUILabel(如 中的设置Interface Builder