创建基于autolayout的指标视图

Bri*_*kel 6 iphone uikit ios autolayout

我有一个可重复使用的视图,我将使用UITableViewCell'sUICollectionViewCell,并需要获取其尺寸tableView:heightForRowAtIndexPath:.一些子视图里面有东西,layoutSubviews所以我不能打电话systemLayoutForContentSize:,而我的计划是:

  1. 实例化指标视图.
  2. 设置大小以包括所需的宽度.
  3. 用数据填充它.
  4. 更新约束/布局子视图.
  5. 抓取视图的高度或内部"大小调整"视图.

我遇到的问题是,我不能强制视图布局而不将其插入视图并等待runloop.

我提炼了一个相当无聊的例子.这是View.xib.子视图未对齐以突出显示视图永远不会布局到基线位置:

View.xib

在主线程上我打电话:

UIView *view = [[UINib nibWithNibName:@"View" bundle:nil] instantiateWithOwner:nil options:nil][0];

NSLog(@"Subviews: %@", view.subviews);
view.frame = CGRectMake(0, 0, 200, 200);

[view updateConstraints];
[view layoutSubviews];
NSLog(@"Subviews: %@", view.subviews);

[self.view addSubview:view];
[view updateConstraints];
[view layoutSubviews];
NSLog(@"Subviews: %@", view.subviews);

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"Subviews: %@", view.subviews);
});
Run Code Online (Sandbox Code Playgroud)

我得到以下视图信息:

1) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
2) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
3) "<UIView: 0x8bad9e0; frame = (50 50; 220 468); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
4) "<UIView: 0x8bad9e0; frame = (0 100; 100 100); autoresize = W+H; layer = <CALayer: 0x8be0070>>"
Run Code Online (Sandbox Code Playgroud)

1表示尚未布置新鲜的NIB视图.2表示updateConstraints/layoutSubviews什么也没做.3表示将其添加到视图层次结构中什么也没做.4最后表明添加到视图层次结构和一个通过主循环布局的视图.

我希望能够获得视图的尺寸,而不必让应用程序处理它或者自己执行手动计算(字符串高度+约束1 +约束2).

更新

我观察到,如果我放在view里面,UIWindow我会得到一点改善:

UIView *view = [[UINib nibWithNibName:@"View" bundle:nil] instantiateWithOwner:nil options:nil][0];
UIWindow *window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
view.frame = CGRectMake(0, 0, 200, 200);
[window addSubview:view];
[view layoutSubviews];
Run Code Online (Sandbox Code Playgroud)

如果view.translatesAutoresizingMaskIntoConstraints == YES,视图的直接子视图将被布局,但他们的孩子都没有.

Aar*_*ger 5

Autolayout问题

在您提到的基本情况下,您可以通过调用setNeedsLayout然后layoutIfNeeded在容器视图上获得正确的大小.

UIView类参考layoutIfNeeded:

使用此方法在绘制之前强制子视图的布局.从接收者开始,只要superviews需要布局,此方法就会向上遍历视图层次结构.然后它展示了祖先下面的整棵树.因此,调用此方法可能会强制整个视图层次结构的布局.UIView的实现调用了等效的CALayer方法,因此具有与CALayer相同的行为.

我不认为"整个视图层次结构"适用于您的用例,因为度量标准视图可能不具有超级视图.

示例代码

在示例空项目中,仅使用此代码,在layoutIfNeeded调用之后确定正确的帧:

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) UIView *redView;

@end

@implementation ViewController

@synthesize redView;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    redView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 220, 468)];
    redView.backgroundColor = [UIColor redColor];
    redView.translatesAutoresizingMaskIntoConstraints = NO;
    [self.view addSubview:redView];

    NSLog(@"Red View frame: %@", NSStringFromCGRect(redView.frame));
    // outputs "Red View frame: {{50, 50}, {220, 468}}"

    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[redView(==100)]" options:0 metrics:Nil views:NSDictionaryOfVariableBindings(redView)]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[redView(==100)]" options:0 metrics:Nil views:NSDictionaryOfVariableBindings(redView)]];

    NSLog(@"Red View frame: %@", NSStringFromCGRect(redView.frame));
    // outputs "Red View frame: {{50, 50}, {220, 468}}"

    [self.view setNeedsLayout];

    NSLog(@"Red View frame: %@", NSStringFromCGRect(redView.frame));
    // outputs "Red View frame: {{50, 50}, {220, 468}}"

    [self.view layoutIfNeeded];

    NSLog(@"Red View frame: %@", NSStringFromCGRect(redView.frame));
    // outputs "Red View frame: {{0, 100}, {100, 100}}"
}

@end
Run Code Online (Sandbox Code Playgroud)

其他考虑因素

稍微超出了您的问题的范围,这里有一些您可能遇到的其他问题,因为我在一个真实的应用程序中处理了这个确切的问题:

  • 计算此值heightForRowAtIndexPath:可能很昂贵,因此您可能需要预先计算并缓存结果
  • 预计算应该在后台线程上完成,但UIView布局不能很好地工作,除非它在主线程上完成
  • 您绝对应该实施estimatedHeightForRowAtIndexPath:以减少这些性能问题的影响

运用 intrinsicContentSize

回应:

一些子视图里面有东西,layoutSubviews所以我不能打电话systemLayoutForContentSize:

如果实现intrinsicContentSize,可以使用此方法,这样可以让视图为自己建议最佳大小.一个实现可能是:

- (CGSize) intrinsicContentSize {
    [self layoutSubviews];
    return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame));
}
Run Code Online (Sandbox Code Playgroud)

这种简单的方法只有在您的layoutSubviews方法不引用已设置的大小(如self.boundsself.frame)时才有效.如果是这样,您可能需要执行以下操作:

- (CGSize) intrinsicContentSize {
    self.frame = CGRectMake(0, 0, 10000, 10000);

    while ([self viewIsWayTooLarge] == YES) {
        self.frame = CGRectInset(self.frame, 100, 100);
        [self layoutSubviews];
    }

    return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame));
}
Run Code Online (Sandbox Code Playgroud)

显然,您需要调整这些值以匹配每个视图的特定布局,并且您可能需要调整性能.

最后,我要补充一点,部分由于使用自动布局的成本呈指数增长,除了最简单的表格单元之外,我通常最终使用手动高度计算.