我将在UITableViewCell's
和UICollectionViewCell
中使用可重复使用的视图,并且需要获取tableView:heightForRowAtIndexPath:
的维度。一些子视图在layoutSubviews
内有内容,因此我无法致电systemLayoutForContentSize:
,而我的计划是:
我遇到的问题是,我不能强制视图布局而不将其插入视图并等待runloop。
我已经提炼出一个相当无聊的例子。这是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);
});
我得到以下观点信息:
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视图。 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];
如果view.translatesAutoresizingMaskIntoConstraints == YES
,则会显示视图的直接子视图,但不会显示他们的子视图。
答案 0 :(得分:5)
在您提到的基本情况下,您可以通过在容器视图上调用setNeedsLayout
然后layoutIfNeeded
来获得正确的大小。
来自UIView class reference on 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
稍微超出了您的问题的范围,以下是您可能遇到的其他一些问题,因为我在真正的应用中处理了这个确切的问题:
heightForRowAtIndexPath:
中计算此内容可能会很昂贵,因此您可能需要预先计算并缓存结果estimatedHeightForRowAtIndexPath:
以减少这些性能问题的影响intrinsicContentSize
回应:
有些子视图在
layoutSubviews
内有内容,所以我无法致电systemLayoutForContentSize:
如果您实施intrinsicContentSize
,则可以使用此方法,该视图可让视图显示自身的最佳大小。对此的一种实现可能是:
- (CGSize) intrinsicContentSize {
[self layoutSubviews];
return CGSizeMake(CGRectGetMaxX(self.bottomRightSubview.frame), CGRectGetMaxY(self.bottomRightSubview.frame));
}
这种简单方法仅在layoutSubviews
方法未引用已设置的大小(例如self.bounds
或self.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));
}
显然,您需要调整这些值以匹配每个视图的特定布局,并且您可能需要调整性能。
最后,我将添加部分归因于exponentially increasing cost of using auto-layout,除了最简单的表格单元格外,我通常最终使用手动高度计算。
答案 1 :(得分:3)
当视图控制器首次加载视图时,您可能正在调用演示代码,例如在viewDidLoad
或其他生命周期方法中。在调用viewDidLayoutSubviews
之前,嵌套子视图的几何不会反映其约束。在视图控制器的初始生命周期中,您没有做任何事情会使该方法更快地到达。
更新12/30/13:在测试了Aaron Brager的示例代码后,我现在意识到前一段不正确。显然,您可以通过调用viewDidLoad
后跟setNeedsLayout
来强制layoutIfNeeded
进行布局。
如果您执行演示代码以响应按钮点击,我认为您会在操作方法完成之前看到嵌套子视图的最终几何图形已记录。
- (IBAction)buttonTapped:(id)sender
{
UIView *view = [[UINib nibWithNibName:@"View" bundle:nil] instantiateWithOwner:nil options:nil][0];
view.frame = CGRectMake(0, 0, 200, 200);
[self.view addSubview:view];
[self.view layoutIfNeeded];
NSLog(@"Subviews: %@", view.subviews);
}
在后一种情况下,您可以按需请求布局,因为视图控制器已完成其初始设置。
但是在视图控制器的初始设置期间,您将如何获得可重用子视图的最终几何图形?
为可重复使用的子视图设置内容后,让视图控制器询问子视图的大小。换句话说,在自定义视图上实现一个方法,根据内容计算大小。
例如,如果子视图的内容是属性字符串,则可以使用boundingRectWithSize:options:context:
之类的方法来帮助确定子视图的大小。
CGRect rect = [attributedString boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) options:NSStringDrawingUsersLineFragmentOrigin context:nil];