我猜问题中显示的 layoutSubviews 覆盖位于 UITableViewCell 的自定义子类上。
这里的问题可能是myImageView不是单元格的直接子视图。
了解单元格对[super layoutSubviews] 的调用仅设置单元格的直接 子视图的框架很重要。它不会在返回之前一直沿着视图层次结构向下走。单元格通常只有一个直接子视图:contentView。所以对[super layoutSubviews] 的调用只是设置了内容视图的框架,然后返回。设置self.myImageView.layer.cornerRadius 的尝试发生得太快了,因为self.myImageView.frame 尚未更新。
稍后,单元格的layoutSubviews方法返回后,UIKit会发送layoutSubviews消息给内容视图,此时内容视图会设置自己的直接子视图的帧。那时myImageView的框架将被设置。
最简单的解决方法是创建一个UIImageView 子类并覆盖其layoutSubviews 以设置其自己的圆角半径。
@interface CircleImageView: UIImageView
@end
@implementation CircleImageView
- (void)layoutSubviews {
[super layoutSubviews];
self.layer.cornerRadius = self.bounds.size.width / 2;
}
@end
更新
Matic Oblak 问道(在评论中),“这是你在这里写的可测试的吗”?
是的,它是可测试的。这是一种测试方法:创建一个视图层次结构,其中包含根视图、根视图内的容器视图和容器视图内的叶视图:
在根视图和容器视图中覆盖 layoutSubviews 以打印视图的框架:
// RootView.m
@implementation RootView {
IBOutlet UIView *_containerView;
IBOutlet UIView *_leafView;
}
- (void)layoutSubviews {
NSLog(@"%s before super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
[super layoutSubviews];
NSLog(@"%s after super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
}
@end
// ContainerView.m
@implementation ContainerView {
IBOutlet UIView *_leafView;
}
- (void)layoutSubviews {
NSLog(@"%s before super: self.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_leafView.frame));
[super layoutSubviews];
NSLog(@"%s after super: self.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_leafView.frame));
}
@end
为了更好地衡量,让我们也看看视图控制器的 viewWillLayoutSubviews 和 viewDidLayoutSubviews 何时运行:
// ViewController.m
@implementation ViewController
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
NSLog(@"%s", __FUNCTION__);
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSLog(@"%s", __FUNCTION__);
}
@end
最后,在与故事板中大小不同的设备或模拟上运行它,然后查看调试控制台:
-[ViewController viewWillLayoutSubviews]
-[RootView layoutSubviews] before super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {280, 420}} _leafView.frame={{20, 20}, {240, 380}}
-[RootView layoutSubviews] after super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ViewController viewDidLayoutSubviews]
-[ContainerView layoutSubviews] before super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] after super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
注意根视图的[super layoutSubviews] 改变了容器视图的框架,但不改变了叶视图的框架。然后,容器视图的[super layoutSubviews] 改变了叶子视图的大小。
因此,当layoutSubviews 在视图中运行时,您可以假设该视图的直接子视图的框架已更新,但您必须假设任何嵌套更深的视图的框架没有 已更新。
还要注意viewDidLayoutSubviews 在视图控制器的视图完成layoutSubviews 之后运行,但在 更深嵌套的后代运行layoutSubviews 之前。所以在viewDidLayoutSubviews,你必须再次假设嵌套更深的视图的框架没有更新。
有一种方法可以强制立即更新嵌套更深的子视图的框架:将layoutIfNeeded 发送到其父视图。比如我可以修改-[RootView layoutSubviews],将layoutIfNeeded发送到_leafView.superview:
- (void)layoutSubviews {
NSLog(@"%s before super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
[super layoutSubviews];
NSLog(@"%s after super: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
[_leafView.superview layoutIfNeeded];
NSLog(@"%s after _leafView.superview: self.frame=%@ _containerView.frame=%@ _leafView.frame=%@", __FUNCTION__, NSStringFromCGRect(self.frame), NSStringFromCGRect(_containerView.frame), NSStringFromCGRect(_leafView.frame));
}
(请注意,在此示例项目中,_leafView.superview 是 _containerView,但一般情况下可能不是。)结果如下:
-[ViewController viewWillLayoutSubviews]
-[RootView layoutSubviews] before super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {280, 420}} _leafView.frame={{20, 20}, {240, 380}}
-[RootView layoutSubviews] after super: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] before super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {240, 380}}
-[ContainerView layoutSubviews] after super: self.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
-[RootView layoutSubviews] after _leafView.superview: self.frame={{0, 0}, {375, 667}} _containerView.frame={{20, 40}, {335, 607}} _leafView.frame={{20, 20}, {295, 567}}
-[ViewController viewDidLayoutSubviews]
所以现在我强制 UIKit 在 -[RootView layoutSubviews] 返回之前更新 _leafView 的框架。
至于“我假设在标签上设置简单文本等许多操作都会调用 setNeedsLayout”:您可以使用我刚刚演示的相同技术来找出该问题的答案。