如何在使用自动布局时保持圆形imageView的圆形?

50

如何将矩形图像视图转换为圆形图像视图,而不必设置宽度和高度的约束条件,并在自动布局中保持形状?因此,允许imageView定义自己的大小,并根据周围对象的先导、尾随、顶部和底部约束来调整大小。

我前几天问过类似的问题,但我觉得这可能更简洁。非常感谢!

编辑

好的,我重新开始,使这个问题尽可能简单。我有一个名为“Cell”的视图,以及其中的一个名为“dog”的UIImageView,就是这样。控制台中再也没有“无法同时满足约束条件”,只是两个简单的使用自动布局的视图。我仍在尝试使用以下代码来使UIImageView变成圆形:

profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true

以下是单元格约束设置:

截屏 1

以下是头像约束设置:

截屏 2

没有代码的结果如下,无舍入,适当的正方形:

截屏 3

加上舍入代码的结果如下:

截屏 4

这对我没有意义,因为没有舍入代码时图像是正方形,而有了代码之后它变成了菱形。如果它是正方形,那么不应该出现问题吗?

编辑 2

当我删除底部约束并将等高度乘数设置为.637时,结果如下:

截屏 5


你目前是如何使图像视图变成圆形的? - Gavin Hope
@GavinHope 我正在将其调用到表视图控制器中的单元格,并使用:cell.ProfileImageView.layer.cornerRadius = cell.ProfileImageView.frame.size.width / 2 cell.ProfileImageView.clipsToBounds = true - CHM
如果您的ImageView宽度不等于高度,则可以在此处找到解决方案:https://dev59.com/zV0b5IYBdhLWcg3wCtTY - lee
15个回答

91

很遗憾,您无法使用 cornerRadius 和自动布局来实现此操作 ——CGLayer 不受自动布局的影响,所以视图大小的任何变化都不会改变一旦设置的半径,从而导致圆形失去其形状。

您可以创建一个自定义的UIImageView子类,并覆盖layoutSubviews方法,以便在每次更改imageview的边界框时设置cornerRadius

编辑

一个示例可能如下所示:

class Foo: UIImageView {
    override func layoutSubviews() {
        super.layoutSubviews()

        let radius: CGFloat = self.bounds.size.width / 2.0

        self.layer.cornerRadius = radius
    }
}

显然,你需要限制 Foobar 实例的宽度与高度相同(以保持圆形)。你可能还想将 Foobar 实例的 contentMode 设置为 UIViewContentMode.ScaleAspectFill,以便它知道如何绘制图像(这意味着图像很可能会被裁剪)。


嗨 @Rupert,谢谢你的信息!我刚开始接触Swift,听起来有点超出我的能力范围。那会是什么样子呢...或者你可能有一个类似解决方案的链接,我可以通过视觉方式查看一下吗? - CHM
非常感谢!当您说“将Foobar实例的宽度限制为与高度相同”时,设置1:1的纵横比是否可行,还是必须给出绝对宽度和高度? - CHM
在你的子类中,例如,你可以添加一个约束 let constraint: NSLayoutConstraint = NSLayoutConstraint(item: self, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Width, multiplier: 1.0, constant: 0.0); self.addConstraint(constraint) - Rupert
这对于iOS 11对我有效,但是当我在iOS 10中测试相同的东西时,它明显表现出抖动.. 在iOS 10中没有发生平稳传输。 - Mehul Thakkar
@MehulThakkar 很难不知道更多细节,但我怀疑这与这个解决方案无关。使用 cornerRadius 显然涉及合成视图,这会导致性能下降。通常这不会被注意到,但也许你在表视图或类似的东西中使用了复杂的视图。 - Rupert
显示剩余3条评论

41

在 viewWillLayoutSubviews 中设置半径将解决问题。

override func viewWillLayoutSubviews() {
  super.viewWillLayoutSubviews()
  profileImageView.layer.cornerRadius = profileImageView.frame.height / 2.0
}

这应该是被接受的答案。简单、实用符合苹果的理念并且完美运作。 - dannysood
Omar Albeik 谢谢 :) - Azharhussain Shaikh
可能应该是 viewDidLayoutSubviews 吗? - JWhitey
viewWillLayoutSubviews()或viewDidLayoutSubviews()都会在viewController中的每次UI更改时触发,例如,在键盘上输入时,这两种方法都会在每次点击时触发。 在这种情况下,我们应该使用这些方法吗?这样安全吗? 我想在autolayout中设置一个具有纵横比的按钮的cornerRadius。 - Sohaib Siddique

11

在你的 .h 文件中创建新的接口,例如:

@interface CornerClip : UIImageView

@end

以.m文件的方式进行实现,例如:

@implementation cornerClip


-(void)layoutSubviews
{
    [super layoutSubviews];

    CGFloat radius = self.bounds.size.width / 2.0;

    self.layer.cornerRadius = radius;

}

@end

现在只需要将您的ImageView设置为“CornerClip”类即可。 100%有效...享受吧


6
首先,我应该提到,如果宽度和高度相等,您可以为您的UIView / UIImageView获得圆形形状。这很重要。在所有其他情况下(当宽度!=高度时),您将无法获得圆形形状,因为您的UI实例的初始形状是矩形。
好的,UIKit SDK为开发人员提供了一种机制来操作UIview的图层实例,以某种方式更改任何图层参数,包括设置一个掩码来用自定义形状替换UIView元素的初始形状。这些工具是IBDesignable / IBInspectable。目标是直接通过Interface Builder预览我们的自定义视图。
因此,使用这些关键字,我们可以编写我们的自定义类,该类仅处理单个条件,即我们是否需要为我们的UI元素设置圆角。
例如,让我们创建从UIImageView扩展的类。
@IBDesignable
class UIRoundedImageView: UIImageView {

    @IBInspectable var isRoundedCorners: Bool = false {
        didSet { setNeedsLayout() }
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        if isRoundedCorners {
            let shapeLayer = CAShapeLayer()
            shapeLayer.path = UIBezierPath(ovalIn:
                CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.width, height: bounds.height
            )).cgPath
            layer.mask = shapeLayer
        }
        else {
            layer.mask = nil
        }

    }

}

在故事板中为UIImageView元素(狗图片所在位置)设置类名后,您将获得一个新选项,出现在属性检查器菜单中(详见截图)。

最终结果应该像这个一样。


4
似乎当您将一个视图作为另一个视图的子视图添加时,该嵌套视图的高度不一定与其父视图相同。这就是问题所在。解决方法是不要将imageView作为子视图添加,而是将其放在backgroundView的顶部。在下面的图像中,我使用UILabel作为我的backgroundView。

screenshot

在你的情况下,当你设置cornerRadius时,请使用以下代码:let radius: CGFloat = self.bounds.size.height / 2.0。这样可以使圆角更加自然。

啊啊啊...我没能让它工作。我根据你说的尝试了这段代码,但是在容器外部运行:let radius: CGFloat = profileImageView.bounds.size.height / 2.0 profileImageView.clipsToBounds = true - CHM
1
这是 Xcode 项目,展示了我所做的事情。https://github.com/RGSSoftware/ImageViewConstraints - Randel S
Randel,用那种方式完全可行。我唯一的问题是,我正在运行这些图像在自定义的tableview单元格中,它们有自己的类文件,并设置为IBOutlets的元素,所以我无法弄清楚如何使用func viewDidLayoutSubviews,因为我正在动态地调用它们到tableView控制器中,使用cellForRowAtIndexPath和dequeueReusableCellWithIdentifier。 - CHM

1

基于omaralbeik的答案,Swift 4+ 的清晰解决方案。

import UIKit

extension UIImageView {

    func setRounded(borderWidth: CGFloat = 0.0, borderColor: UIColor = UIColor.clear) {
        layer.cornerRadius = frame.width / 2
        layer.masksToBounds = true
        layer.borderWidth = borderWidth
        layer.borderColor = borderColor.cgColor
    }
}

在UIViewController中的使用示例

1. 简单地使UIImageView变为圆角

override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        imageView.setRounded()
    }

2.带有边框宽度和颜色的圆角UIImageView

override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()

        imageView.setRounded(borderWidth: 1.0, borderColor: UIColor.red)
    }

1

我有一个巧妙的解决方案,可以让你在改变框架大小的同时得到平滑的圆角动画。

假设你有一个名为 ViewSubclass 的 UIView 子类。它应该包含以下代码:

class ViewSubclass: UIView {
    var animationDuration : TimeInterval?
    let imageView = UIImageView()
    //some imageView setup code

    override func layoutSubviews() {
        super.layoutSubviews()

        if let duration = animationDuration {
            let anim = CABasicAnimation(keyPath: "cornerRadius")
            anim.fromValue = self.imageView.cornerRadius
            let radius = self.imageView.frame.size.width / 2
            anim.toValue = radius
            anim.duration = duration
            self.imageView.layer.cornerRadius = radius
            self.imageView.layer.add(anim, forKey: "cornerRadius")
        } else {
            imageView.cornerRadius = imageView.frame.size.width / 2
        }

        animationDuration = nil
    }
}

然后你将不得不这样做:

let duration = 0.4 //For example
instanceOfViewSubclass.animationDuration = duration
UIView.animate(withDuration: duration, animations: { 
    //Your animation
    instanceOfViewSubclass.layoutIfNeeded()
})

虽然不够美观,也可能无法处理复杂的多重动画,但确实回答了这个问题。


1

写下这段代码

override viewDidLayoutSubViews() {
profileImageView.layer.cornerRadius = profileImageView.frame.size.width / 2
profileImageView.clipsToBounds = true

}

在这种情况下,它将在计算自动布局计算之后被调用。在第一段代码中,您在计算视图的实际大小之前调用了cornerradius代码,因为它是根据纵横比动态计算的,实际的角半径是在等于视图的宽度和高度之前计算的。

0
如果您已经创建了UIImageView的子类,那么只需在其中添加这段神奇的代码即可。
编写语言:Swift 3
override func layoutSubviews() {
    super.layoutSubviews()
    if self.isCircular! {
        self.layer.cornerRadius = self.bounds.size.width * 0.50
    }
}

0

我对iOS原生开发还比较新,但我曾经遇到过同样的问题并找到了解决方案。

this was the goal

因此,绿色背景具有以下限制:

backgroundView.translatesAutoresizingMaskIntoConstraints = false
backgroundView.topAnchor.constraint(equalTo: superview!.safeAreaLayoutGuide.topAnchor).isActive = true
backgroundView.leftAnchor.constraint(equalTo: superview!.leftAnchor).isActive = true
backgroundView.widthAnchor.constraint(equalTo: superview!.widthAnchor).isActive = true
backgroundView.heightAnchor.constraint(equalTo: superview!.heightAnchor, multiplier: 0.2).isActive = true

图片限制:

avatar.translatesAutoresizingMaskIntoConstraints = false
avatar.heightAnchor.constraint(equalTo: backgroundView.heightAnchor, multiplier: 0.8).isActive = true
avatar.widthAnchor.constraint(equalTo: backgroundView.heightAnchor, multiplier: 0.8).isActive = true
avatar.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor, constant: 0).isActive = true
avatar.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 20).isActive = true

viewWillLayoutSubviews() 方法中,我将圆角半径设置为
avatar.layer.cornerRadius = (self.frame.height * 0.2 * 0.8) / 2

基本上,我只是计算图像的高度,然后将其除以2。0.2是backgroundView高度约束的乘数,0.8是图像宽度/高度约束的乘数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接