如何将UIView作为带有Xcode 8的圆角半径的圆

时间:2016-09-26 12:31:20

标签: ios objective-c uiview cornerradius

我在视图上设置角半径时遇到问题,因为自从Xcode更新到8后,视图的帧在大多数情况下设置为1000x1000而不是适当的大小。

有一个类似的问题here但我希望添加更多信息,希望有人找到答案或解决方法。

在其中一个实例中,我有一个表格视图单元格,其中包含在故事板中创建的图像视图。它的宽度和高度设置为约束常数(至95)。角半径在layoutSubviews中设置,目前一直在使用:

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
}

所以我在试验和错误中玩了一下,如果我尝试通过调用设置图像(从远程服务器检索图像)后尝试触发布局,情况似乎会好一点:

self.myImageView.image = image;
[self.myImageView setNeedsLayout];
[self setNeedsLayout];
[self layoutIfNeeded];

在大多数情况下,这仍然不起作用。现在有趣的部分:

我在标签栏上有一个这种视图控制器的情况,当我按下目标视图控制器的标签时,会立即调用布局子视图(来自UIApplicationMain),其中图像视图的大小是1000x1000但是一旦设置了图像并且我调用了布局方法,那么再次调用布局子视图,正确的图像视图大小为95x95,得到正确的结果。

第二种情况是推动相同类型的视图控制器,现在首先不调用布局,在图像设置和强制布局之后进行唯一的调用。结果是再次具有大小为1000x1000的图像视图,将角半径设置为500并且图像不可见。

那么有一个很好的解决方案来修复这些奇怪的帧值吗?我在多个视图控制器,表视图单元格上遇到同样的问题。这一切都在更新之前一直有效。

有人有任何想法甚至是这个问题的根源是什么?是故事板还是某种设置迁移,还是这个版本的软件引入了一些布局错误?

我现在还从笔尖添加了清醒:

- (void)awakeFromNib
{
    [super awakeFromNib];

    [self.myImageView setNeedsLayout];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

现在强制布局在我的情况下至少发生2次,第二次框架是正确的。这仍然远不是答案,这很糟糕,我想要它,因为我需要在每个图像视图,按钮的所有单元格上使用它。

7 个答案:

答案 0 :(得分:9)

您可以将方法更新为

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
    self.myImageView.clipsToBounds = TRUE;
}

并试一试。

考虑到我的一个案例,我错过了设置clipsToBounds = TRUE;并且没有让它成为圈子。

答案 1 :(得分:4)

我想问题中显示的layoutSubviews覆盖是在UITableViewCell的自定义子类上。

这里的问题可能是myImageView不是细胞的直接子视图。

了解小区对[super layoutSubviews]的调用仅设置了小区的直接子视图的帧,这一点非常重要。在返回之前,它不会一直走到视图层次结构中。单元格通常只有一个直接子视图:contentView。因此,对[super layoutSubviews]的调用只是设置内容视图的框架然后返回。设置self.myImageView.layer.cornerRadius的尝试过早发生,因为self.myImageView.frame尚未更新。

稍后,在单元格的layoutSubviews方法返回后,UIKit会将layoutSubviews消息发送到内容视图,此时内容视图将设置自己的直接子视图' s帧。那就是设置myImageView的帧时。

最简单的解决方法是创建一个UIImageView子类并覆盖其layoutSubviews以设置自己的角半径。

@interface CircleImageView: UIImageView
@end

@implementation CircleImageView

- (void)layoutSubviews {
    [super layoutSubviews];
    self.layer.cornerRadius = self.bounds.size.width / 2;
}

@end

更新

Matic Oblak(在评论中)问道,“这是你在这里写的可测试的吗?”

是的,它是可测试的。以下是测试它的一种方法:使用根视图创建视图层次结构,在根视图中创建容器视图,并在容器视图内创建叶视图:

view hierarchy

在根视图和容器视图中覆盖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

为了更好地衡量,让我们看看视频控制器的viewWillLayoutSubviewsviewDidLayoutSubviews何时运行:

// 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在_leafView返回之前更新-[RootView layoutSubviews]的框架。

至于“我假设在标签上设置简单文本等许多操作都会调用setNeedsLayout”:您可以使用我刚才演示的相同技术来找出该问题的答案。

答案 2 :(得分:0)

我创建了一个演示应用程序,看看这个问题发生的最低要求是什么,而且非常低:

  • 创建新的单一视图应用程序
  • 在主视图控制器上添加表格视图
  • 将一个单元格添加到表视图中,并将其类重写为新的表视图单元子类
  • 将图像视图添加到具有固定宽度和高度约束的单元格(添加前导和顶部约束)
  • 在单元格layoutSubviews中,将图像视图的半径设置为图像视图宽度的一半(结果将始终为500)

由于转角半径为500,因此不显示图像视图。

所以我正在寻找一个最小的修复,从以前的测试看来,布局似乎缺少第一次调用,所以我通过覆盖nib的清醒来添加它:

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self setNeedsLayout];
    [self layoutIfNeeded];
}

所以在我正在研究的项目中,我创建了一个表视图单元格的新子类,并且我已经从nib中重写了清醒。然后我将所有其他单元格作为这个单元的子类。它不是一个解决方案,但它是一种解决方法,因为我有57个表视图单元子类,所以这个程序很快实现。

答案 3 :(得分:0)

尝试覆盖UIImageView子类中的drawRect:方法,然后设置角半径。

- (void)drawRect:(CGRect)rect {
self.layer.cornerRadius = self.frame.size.width/2.0f;
[self setClipsToBounds:YES];
}

答案 4 :(得分:0)

在圆形视图中添加@pushkraj答案,你的UIView宽度和高度应相同。

然后你可以做。

- (void)layoutSubviews
{
   [super layoutSubviews];

   view.layer.cornerRadius = view.frame.size.height/2
   view.clipsToBounds = true
}

答案 5 :(得分:-1)

你可以这样做

- (void)layoutSubviews
{
    [super layoutSubviews];
    self.myImageView.layer.cornerRadius = self.myImageView.frame.size.width/2.0f;
    self. myImageView.layer.masksToBounds = YES;
}

这对你有用。 了解为什么需要屏蔽图层到边界 link here

答案 6 :(得分:-3)

要制作任何视图圆形,您必须更新圆角半径和圆角半径是任意视图layer的属性(viewInfo)。

来自Apple文档

将半径设置为大于0.0的值会导致图层开始在其背景上绘制圆角。默认情况下,圆角半径不适用于图层内容属性中的图像;它仅适用于图层的背景颜色和边框。但是,将masksToBounds属性设置为true会导致内容被裁剪为圆角。

此属性的默认值为0.0。

More about corner radius

以下是与swift 4

兼容的代码段
@IBOutlet weak var viewInfo: UIView!

func setCornerRadius(){
    self.viewInfo.layer.cornerRadius = self.viewInfo.frame.size.height/2
}