ios - 创建基于自动布局的指标 View

标签 ios iphone uikit autolayout

我有一个可重复使用的 View ,我将在 UITableViewCell 的UICollectionViewCell 中使用,并且需要为 tableView:heightForRowAtIndexPath:。某些 subview 在 layoutSubviews 中进行操作,因此我无法调用 systemLayoutForContentSize:,我的计划是:

  1. 实例化指标 View 。
  2. 设置大小以包含所需的宽度。
  3. 用数据填充它。
  4. 更新约束/布局 subview 。
  5. 获取 View 或内部“调整大小” View 的高度。

我遇到的问题是,如果不将 View 插入 View 并等待运行循环,我无法强制 View 进行布局。

我提炼了一个相当无聊的例子。这是 View.xib。 subview 未对齐以突出显示该 View 甚至从未布局到基线位置:

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);
});

我得到如下 View 信息:

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

1 表示尚未布局新的 NIB View 。 2 表示 updateConstraints/layoutSubviews 什么也没做。 3 表示将它添加到 View 层次结构中没有任何作用。 4 最后表示添加到 View 层次结构并通过主循环布置 View 。

我希望能够获得 View 的尺寸,而无需让应用程序自行处理或执行手动计算(字符串高度 + 约束 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];

如果 view.translatesAutoresizingMaskIntoConstraints == YES, View 的直接 subview 将被布置,但不会布置它们的 subview 。

最佳答案

自动布局问题

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

来自UIView class reference on layoutIfNeeded :

Use this method to force the layout of subviews before drawing. Starting with the receiver, this method traverses upward through the view hierarchy as long as superviews require layout. Then it lays out the entire tree beneath that ancestor. Therefore, calling this method can potentially force the layout of your entire view hierarchy. The UIView implementation of this calls the equivalent CALayer method and so has the same behavior as CALayer.

我认为“整个 View 层次结构”不适用于您的用例,因为指标 View 可能没有父 View 。

示例代码

在一个示例空项目中,仅使用此代码,在调用 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

其他注意事项

稍微超出了您的问题范围,以下是您可能会遇到的其他一些问题,因为我已经在真实应用中解决了这个确切的问题:

  • heightForRowAtIndexPath: 中进行计算可能开销很大,因此您可能需要预先计算并缓存结果
  • 预计算应该在后台线程上完成,但 UIView 布局除非在主线程上完成才能正常工作
  • 您绝对应该实现 estimatedHeightForRowAtIndexPath: 以减少这些性能问题的影响

使用intrinsicContentSize

回应:

Some subviews have stuff going on inside layoutSubviews so I can't call systemLayoutForContentSize:

如果您实现了 intrinsicContentSize,则可以使用此方法,它可以让 View 为自己建议最佳大小。一种实现方式可能是:

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

这种简单的方法仅在您的 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));
}

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

最后,我将部分添加到 exponentially increasing cost of using auto-layout ,除了最简单的表格单元格外,我通常会使用手动高度计算结束。

关于ios - 创建基于自动布局的指标 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20713533/

相关文章:

iOS - 方向改变时导航栏不显示大标题

ios - 重新创建谷歌收件箱的 ios 加号按钮

ios - 使用调度队列快速下载

ios - 自定义 UIView 子类中的 Swift UIView subview 不圆角

ios - 您将如何解决以下内存泄漏问题?

ios - 从其他View Controller访问开关值

iphone - 模拟器和设备上的核心位置

iphone - 使用深层 UINavigationController 保存 iPhone 程序状态

ios - SwiftUI 中带有 UIViewRepresentable 的 UITableView

当 autoScales 设置为 true 时,iOS PDFView 会导致底层断言