如何为带有遮罩的圆形图像添加边框

3

这是我的尝试:

func round() {
    let width = bounds.width < bounds.height ? bounds.width : bounds.height
    let mask = CAShapeLayer()
    mask.path = UIBezierPath(ovalInRect: CGRectMake(bounds.midX - width / 2, bounds.midY - width / 2, width, width)).CGPath

    self.layer.mask = mask

    // add border
    let frameLayer = CAShapeLayer()
    frameLayer.path = mask.path
    frameLayer.lineWidth = 4.0
    frameLayer.strokeColor = UIColor.whiteColor().CGColor
    frameLayer.fillColor = nil

    self.layer.addSublayer(frameLayer)
}

它在 iPhone 6 模拟器上运行正常(storyboard 的大小为 4.7),但在 5s 和 6+ 上看起来很奇怪:

enter image description here

enter image description here

这是自动布局的问题吗?没有边框,自动布局正常工作。这是我第一次使用蒙版,所以我不确定我所做的是否正确。

round 函数在 viewDidLayoutSubviews 中调用。

有什么想法吗?


1
阅读viewDidLayoutSubviews的描述,它会告诉你何时调用该方法,但它似乎不完全适合你想要实现的目标。 - A-Live
那么我的选择是什么? - Adrian
1
创建一个视图类的子类,存储您添加的路径和图层的引用,在检测到大小更改时修改它们(使用 layoutSubviews),以便将来重复使用。 - A-Live
你能给我一个例子吗?我对这个很新。 - Adrian
2个回答

17
如果您已经对UIImageView进行了子类化,例如,您可以重写layoutSubviews方法,使其(a)更新蒙版;(b)删除任何旧的边框;和(c)添加一个新的边框。在Swift 3中:
import UIKit

@IBDesignable
class RoundedImageView: UIImageView {

    /// saved rendition of border layer

    private weak var borderLayer: CAShapeLayer?

    override func layoutSubviews() {
        super.layoutSubviews()

        // create path

        let width = min(bounds.width, bounds.height)
        let path = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: width / 2, startAngle: 0, endAngle: .pi * 2, clockwise: true)

        // update mask and save for future reference

        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.mask = mask

        // create border layer

        let frameLayer = CAShapeLayer()
        frameLayer.path = path.cgPath
        frameLayer.lineWidth = 32.0
        frameLayer.strokeColor = UIColor.white.cgColor
        frameLayer.fillColor = nil

        // if we had previous border remove it, add new one, and save reference to new one

        borderLayer?.removeFromSuperlayer()
        layer.addSublayer(frameLayer)
        borderLayer = frameLayer
    }
}

那样,它会响应布局的更改,但它会确保清除任何旧边框。
顺便说一下,如果您不是子类化UIImageView,而是将此逻辑放在视图控制器中,则应该重写UIView的viewWillLayoutSubviews而不是layoutSubviews。但基本思想是相同的。

--

顺便说一下,我在使用这个形状图层时会与遮罩一起使用,因为如果你仅仅应用了一个UIView的圆角,它可能会导致奇怪的伪影(看看圆形边框下面非常细的灰线):

artifact

如果您使用贝塞尔路径方法,则不会出现这样的伪影:

no artifact

关于Swift 2.3的示例,请参阅此答案的早期版本


1
太棒了,这解决了我所有的问题。非常感谢。 - Adrian
工作顺畅,但我们不能在绘制方法中执行此操作吗? - Nikhil Manapure
1
哇,太棒了。在SA上有很多类似的问题都无法解决这个问题,但你的回答非常好。 - Michael Ramos
@NikhilManapure - 是的,你可以在draw(_:)方法中手动遮罩和渲染,但我们通常避免这样做,因为理论上会失去Apple实现的任何优化/功能。此外,这是一种通用机制,无论视图类型或其可能具有的任何子视图都可以使用。 - Rob

4
最简单的方法是操作图像视图本身的layer
imageView.layer.cornerRadius = imageView.bounds.size.width / 2.0
imageView.layer.borderWidth = 2.0
imageView.layer.borderColor = UIColor.whiteColor.CGColor
imageView.layer.masksToBounds = true

你可以在viewDidLayoutSubviews或者layoutSubviews中包含此代码,以确保尺寸始终正确。

注意:也许这种技术会让你的圆形遮罩变得过时。一般来说,总是选择最简单的解决方案。


我之前一直在使用这个,但是自动布局方面出了很多问题。 - Adrian
1
所以你应该使用这个来解决你的自动布局问题。 - Mundi
2
我会添加 imageView.layer.masksToBounds = true - NRitH
我指的是我之前使用了cornerRadius这个解决方案,同时将maskToBounds设置为true,但是我的图片需要在不同屏幕上进行调整大小,并且出现了许多问题,我无法解决它们,所以我决定尝试使用遮罩。 - Adrian
1
“cornerRadius”方法因在角落处存在不良瑕疵而臭名昭著。我建议修复“UIBezierPath”方法(正如上面评论中A-Live所建议的)。 - Rob
显示剩余7条评论

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