【问题标题】:How to resize superview to fit all subviews with autolayout?如何调整超级视图的大小以适应所有具有自动布局的子视图?
【发布时间】:2013-08-09 16:38:35
【问题描述】:

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

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

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

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

【问题讨论】:

  • 您应该选择以下答案之一,以确保 Tom Swifts 回答了您的问题。所有发帖者都花费了大量时间来帮助您,现在您应该尽自己的一份力量,选择您最喜欢的答案。

标签: ios uitableview autolayout


【解决方案1】:

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

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

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

【讨论】:

  • 这是一个很棒的技巧,但并不为人所知。重复一遍:如果内部视图具有固有高度并固定在顶部和底部,则外部视图不需要指定其高度,实际上会拥抱其内容。您可能需要为内部视图调整内容压缩和拥抱以获得所需的结果。
  • 这是钱!我见过的更简洁的 VFL 示例之一。
  • 这就是我要找的东西(谢谢@John),所以我发布了这个here的Swift版本@
  • “您必须选择哪个子视图将驱动容器的高度和/或宽度......最常见的是您放置在整个 UI 右下角的任何 UI 元素” .. 我正在使用 PureLayout,这对我来说是关键。对于一个父视图,一个子视图,将子视图固定在右下角,突然给父视图一个大小。谢谢!
  • 选择一种视图来驱动宽度正是我无法做到的。有时一个子视图更宽,有时另一个子视图更宽。对这种情况有什么想法吗?
【解决方案2】:

您可以通过创建一个约束并通过接口构建器连接它来做到这一点

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

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

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

我希望这可以澄清它。

【讨论】:

  • 因此可以从源代码配置在 XCode 中创建的约束。但问题是如何配置它们以调整超级视图的大小。
  • 是的@DAK。请确保超级视图布局。将约束放在超级视图上并设置高度约束,这样每当您的子视图增加时,它就会自动增加超级视图约束高度。
  • 这也对我有用。我有固定大小的单元格(高度为 60 像素)会有所帮助。因此,当视图加载时,我将 IBOutlet 的高度约束设置为 60 * x,其中 x 是单元格的数量。最终,您可能希望在滚动视图中使用它,以便查看整个内容。
【解决方案3】:

这可以在较大的UIView 中对普通的subview 执行,但对于headerViews 不会自动起作用。 headerView 的高度由 tableView:heightForHeaderInSection: 返回的内容决定,因此您必须根据 heightheight 加上 UIButton 和您需要的任何 padding 的空间来计算 height .你需要做这样的事情:

-(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 是您要填充UILabel 的任何字符串,而281 数字是UILabelwidth(如Interface Builder 中的设置)

【讨论】:

  • 不幸的是,这不起作用。超级视图上的“大小以适合内容”删除了将按钮底部连接到超级视图的约束,正如您所预测的那样。在运行时,标签会调整大小并按下按钮,但超级视图不会自动调整大小。
  • @DAK,抱歉,问题是您的视图是表视图的标题。我误解了你的情况。我在想包含按钮和标签的视图在另一个视图内,但听起来你正在使用它作为标题(而不是作为标题内的视图)。所以,我原来的答案是行不通的。我已将答案更改为我认为应该可行的方法。
  • 这与我当前的实现类似,但我希望有更好的方法。我宁愿避免每次更改标题时都更新此代码。
  • @Dak,抱歉,我认为没有更好的方法,这就是表格视图的工作方式。
  • 请看我的回答。 “更好的方法”是使用 UIView systemLayoutSizeFittingSize:。也就是说,在视图或单元格上执行完全自动布局只是为了在 tableview 中获得所需的高度是相当昂贵的。我会为复杂的单元格做这件事,但对于更简单的情况,可能会回退到像你这样的解决方案。
【解决方案4】:

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

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

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

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

使用自动布局计算表格单元格的高度不是很高效,但确实很方便,尤其是在您的单元格布局复杂的情况下。

如上所述,如果您使用多行 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 推断出来的:

【讨论】:

  • 你能举一个例子来说明 heightForRowAtIndexPath: 在包含多行标签的单元格中使用这个方法会是什么样子?我已经搞砸了很多,还没有让它工作。你如何得到一个单元格(特别是如果单元格是在故事板中设置的)?您需要什么限制才能使其发挥作用?
  • @rdelmar - 当然。我在答案中添加了一个示例。
  • 这对我不起作用,直到我添加了一个从单元格底部到单元格中最低子视图底部的最终垂直约束。似乎垂直约束必须包括单元格及其内容之间的顶部和底部垂直间距才能成功计算单元格高度。
  • 我只需要一个技巧就可以让它工作。 @EricBaker 的小费终于成功了。谢谢分享那个人。我现在得到了非零大小,无论是针对 sizingCell 还是它的 contentView
  • 对于那些无法获得正确高度的人,请确保您的 sizingCell 的宽度与您的 tableViews 的宽度相匹配。
猜你喜欢
  • 2013-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多