Swift UIBezierPath:如何在具有圆角的视图周围添加边框?

Swift UIBezierPath:如何在具有圆角的视图周围添加边框?,swift,uibezierpath,Swift,Uibezierpath,我正在使用UIBezierPath使imageview具有圆角,但我还想为imageview添加边框。请记住,顶部是uiimage,底部是标签 当前使用此代码生成: let rectShape = CAShapeLayer() rectShape.bounds = myCell2.NewFeedImageView.frame rectShape.position = myCell2.NewFeedImageView.center rectShape.path = UIBezierPath(rou

我正在使用UIBezierPath使imageview具有圆角,但我还想为imageview添加边框。请记住,顶部是uiimage,底部是标签

当前使用此代码生成:

let rectShape = CAShapeLayer()
rectShape.bounds = myCell2.NewFeedImageView.frame
rectShape.position = myCell2.NewFeedImageView.center
rectShape.path = UIBezierPath(roundedRect: myCell2.NewFeedImageView.bounds,
    byRoundingCorners: .TopRight | .TopLeft,
    cornerRadii: CGSize(width: 25, height: 25)).CGPath
myCell2.NewFeedImageView.layer.mask = rectShape

我想添加一个绿色边框,但我不能使用

myCell2.NewFeedImageView.layer.borderWidth = 8
myCell2.NewFeedImageView.layer.borderColor = UIColor.greenColor().CGColor
因为它切断了边框的左上角和右上角,如图所示:


有没有办法在我当前的代码中添加带有UIBezierPath的边框?

当然有!每个视图都有一个
图层
属性(通过给图层圆角可以知道)。
上的另外两个属性是
borderColor
borderWidth
。只需设置这些,您就可以在视图中添加边框!(边框将跟随圆角。)确保使用
UIColor.CGColor
作为
borderColor
的普通
UIColor
与类型不匹配。

您可以重用UIBezierPath路径,并向视图添加形状层。下面是视图控制器内部的一个示例

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create a view with red background for demonstration
        let v = UIView(frame: CGRectMake(0, 0, 100, 100))
        v.center = view.center
        v.backgroundColor = UIColor.redColor()
        view.addSubview(v)

        // Add rounded corners
        let maskLayer = CAShapeLayer()
        maskLayer.frame = v.bounds
        maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
        v.layer.mask = maskLayer

        // Add border
        let borderLayer = CAShapeLayer()
        borderLayer.path = maskLayer.path // Reuse the Bezier path
        borderLayer.fillColor = UIColor.clearColor().CGColor
        borderLayer.strokeColor = UIColor.greenColor().CGColor
        borderLayer.lineWidth = 5
        borderLayer.frame = v.bounds
        v.layer.addSublayer(borderLayer)   
    }

}
最终结果是这样的

请注意,只有当视图的大小固定时,此选项才能按预期工作。当视图可以调整大小时,您需要创建一个自定义视图类,并在
布局子视图中调整图层大小,如上所述:

要做到完美并不容易。 这是一个简单的解决方案


这个

  • 正确解决您正在绘制边界线一半的问题

  • 在自动布局中完全可用

  • 当视图大小更改或设置动画时,完全重新工作

  • 是完全可设计的-你可以在你的故事板上实时看到它

2019年

@IBDesignable
class RoundedCornersAndTrueBorder: UIView {
    @IBInspectable var cornerRadius: CGFloat = 10 {
        didSet { setup() }
    }
    @IBInspectable var borderColor: UIColor = UIColor.black {
        didSet { setup() }
    }
    @IBInspectable var trueBorderWidth: CGFloat = 2.0 {
        didSet { setup() }
    }
    
    override func layoutSubviews() {
        setup()
    }
    
    var border:CAShapeLayer? = nil
    
    func setup() {
        // make a path with round corners
        let path = UIBezierPath(
          roundedRect: self.bounds, cornerRadius:cornerRadius)
        
        // note that it is >exactly< the size of the whole view
        
        // mask the whole view to that shape
        // note that you will ALSO be masking the border we'll draw below
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
        
        // add another layer, which will be the border as such
        
        if (border == nil) {
            border = CAShapeLayer()
            self.layer.addSublayer(border!)
        }
        // IN SOME APPROACHES YOU would INSET THE FRAME
        // of the border-drawing layer by the width of the border
        // border.frame = bounds.insetBy(dx: borderWidth, dy: borderWidth)
        // so that when you draw the line, ALL of the WIDTH of the line
        // DOES fall within the actual mask.
        
        // here, we will draw the border-line LITERALLY ON THE EDGE
        // of the path. that means >HALF< THE LINE will be INSIDE
        // the path and HALF THE LINE WILL BE OUTSIDE the path
        border!.frame = bounds
        let pathUsingCorrectInsetIfAny =
          UIBezierPath(roundedRect: border!.bounds, cornerRadius:cornerRadius)
        
        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor
        
        // the following is not what you want:
        // it results in "half-missing corners"
        // (note however, sometimes you do use this approach):
        //border.borderColor = borderColor.cgColor
        //border.borderWidth = borderWidth
        
        // this approach will indeed be "inside" the path:
        border!.strokeColor = borderColor.cgColor
        border!.lineWidth = trueBorderWidth * 2.0
        // HALF THE LINE will be INSIDE the path and HALF THE LINE
        // WILL BE OUTSIDE the path. so MAKE IT >>TWICE AS THICK<<
        // as requested by the consumer class.
        
    }
}
@IBDesignable
类RoundCorners和TrueBorder:UIView{
@i可检测的var转角半径:CGFloat=10{
didSet{setup()}
}
@IBInspectable var borderColor:UIColor=UIColor.black{
didSet{setup()}
}
@IBInspectable变量trueBorderWidth:CGFloat=2.0{
didSet{setup()}
}
覆盖func布局子视图(){
设置()
}
var边界:CAShapeLayer?=零
函数设置(){
//做一条有圆角的路
let path=UIBezierPath(
roundedRect:self.bounds,cornerRadius:cornerRadius)
//请注意,它>正好<整个视图的大小
//将整个视图遮罩为该形状
//请注意,您还将屏蔽我们将在下面绘制的边界
let mask=CAShapeLayer()
mask.path=path.cgPath
self.layer.mask=掩码
//添加另一层,该层将作为边界
如果(边框==nil){
border=CAShapeLayer()
self.layer.addSublayer(边框!)
}
//在某些方法中,您可以插入框架
//按边框宽度设置边框绘制图层的宽度
//border.frame=bounds.insetBy(dx:borderWidth,dy:borderWidth)
//所以当你画这条线的时候,线的所有宽度
//不在实际遮罩范围内。
//在这里,我们将在边缘上绘制边界线
//这意味着>一半<线将在内部
//这条路和半条线将在这条路的外面
边框!.frame=bounds
让PathUsingCorrectInsertifany=
UIBezierPath(roundedRect:border!.bounds,cornerRadius:cornerRadius)
border!.path=PathUsingCorrectInsertifany.cgPath
border!.fillColor=UIColor.clear.cgColor
//以下不是您想要的:
//它会导致“半缺角”
//(但请注意,有时您确实使用这种方法):
//border.borderColor=borderColor.cgColor
//border.borderWidth=borderWidth
//这种方法确实是“内部”路径:
border!.strokeColor=borderColor.cgColor
边框!.lineWidth=trueBorderWidth*2.0
//半条线将在路径内,半条线
//将在路径之外。因此,使其>>两倍于绝对完美的2019解决方案

不用再多说了,下面就是你如何做到这一点的

  • 不要实际使用视图附带的“基本”层
  • 仅为图像创建一个新层。现在可以(循环)遮罩该层而不影响下一层
  • 为边界创建一个新层,这样它就不会被图片层屏蔽
  • 关键事实是

  • 使用CALayer,您确实可以应用.mask,它只影响该层
  • 在绘制圆(或任何边界)时,必须非常小心地注意这样一个事实,即您只能获得“一半宽度”——简言之,从不使用与绘制的路径相同的进行裁剪
  • 请注意,原始cat图像的宽度与水平黄色箭头的宽度完全相同。您必须小心地绘制图像,以便整个图像显示在圆形中,小于整个自定义控件
  • 所以,以通常的方式进行设置

    import UIKit
    
    @IBDesignable class GreenCirclePerson: UIView {
        
        @IBInspectable var borderColor: UIColor = UIColor.black { didSet { setup() } }
        @IBInspectable var trueBorderThickness: CGFloat = 2.0 { didSet { setup() } }
        @IBInspectable var trueGapThickness: CGFloat = 2.0 { didSet { setup() } }
        
        @IBInspectable var picture: UIImage? = nil { didSet { setup() } }
        
        override func layoutSubviews() { setup() }
        
        var imageLayer: CALayer? = nil
        var border: CAShapeLayer? = nil
        
        func setup() {
            
            if (imageLayer == nil) {
                imageLayer = CALayer()
                self.layer.addSublayer(imageLayer!)
            }
            if (border == nil) {
                border = CAShapeLayer()
                self.layer.addSublayer(border!)
            }
            
    
    现在,仔细制作圆形裁剪图像的图层:

            // the ultimate size of our custom control:
            let box = self.bounds.aspectFit()
            
            let totalInsetOnAnyOneSide = trueBorderThickness + trueGapThickness
            
            let boxInWhichImageSits = box.inset(by:
               UIEdgeInsets(top: totalInsetOnAnyOneSide, left: totalInsetOnAnyOneSide,
               bottom: totalInsetOnAnyOneSide, right: totalInsetOnAnyOneSide))
            
            // just a note. that version of inset#by is much clearer than the
            // confusing dx/dy variant, so best to use that one
            
            imageLayer!.frame = boxInWhichImageSits
            imageLayer!.contents = picture?.cgImage
            imageLayer?.contentsGravity = .resizeAspectFill
            
            let halfImageSize = boxInWhichImageSits.width / 2.0
            
            let maskPath = UIBezierPath(roundedRect: imageLayer!.bounds,
               cornerRadius:halfImageSize)
            let maskLayer = CAShapeLayer()
            maskLayer.path = maskPath.cgPath
            imageLayer!.mask = maskLayer
            
    
    接下来,作为一个完全独立的层,根据需要绘制边界:

            // now create the border
            
            border!.frame = bounds
            
            // To draw the border, you must inset it by half the width of the border,
            // otherwise you'll be drawing only half the border. (Indeed, as an additional
            // subtle problem you are clipping rather than rendering the outside edge.)
            
            let halfWidth = trueBorderThickness / 2.0
            let borderCenterlineBox = box.inset(by:
                UIEdgeInsets(top: halfWidth, left: halfWidth,
                bottom: halfWidth, right: halfWidth))
            
            let halfBorderBoxSize = borderCenterlineBox.width / 2.0
            
            let borderPath = UIBezierPath(roundedRect: borderCenterlineBox,
              cornerRadius:halfBorderBoxSize)
            
            border!.path = borderPath.cgPath
            border!.fillColor = UIColor.clear.cgColor
            
            border!.strokeColor = borderColor.cgColor
            border!.lineWidth = trueBorderThickness
        }
    }
    
    在iOS标准控件中,一切都可以完美运行:

    所有看不见的东西都是看不见的;您可以通过整个自定义控件看到后面的任何材质,没有“半厚度”问题或缺少图像材质,您可以按常规方式设置自定义控件背景颜色等。检查器控件都能正常工作。(呸!)

    类似的解决方案:

    -图像+圆形+阴影
    -双角问题
    - "