swift UIBezierPath:如何在圆角视图周围添加边框?

qaxu7uf2  于 2023-04-28  发布在  Swift
关注(0)|答案(5)|浏览(292)

我正在使用UIBezierPath使我的imageview具有圆角,但我还想为imageview添加边框。请记住,顶部是一个uimage,底部是一个标签。
当前使用此代码生成:

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的边框?

bjp0bcyl

bjp0bcyl1#

可以重用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)   
    }

}

最终结果如下所示。

请注意,只有当视图的大小固定时,这才能按预期工作。当视图可以调整大小时,您需要创建一个自定义视图类并调整layoutSubviews中的图层大小。

qlckcl4x

qlckcl4x2#

正如上面所说:

要做到完美并不容易。

这里有一个简易的解决方案。
这个

  • 正确解决了您正在绘制边界线的***一半的问题
  • 完全可以用于自动布局
  • 当视图的大小改变或动画时,完全重新工作

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在Xcode故事板中完全被破坏(2023),所以,与任何IBDesignable一样,你不会“在故事板上看到它”。)

评论中问题的初学者帮助。..

1.创建一个名为“Fattie”的“新Swift文件”。(注意,有趣的是,你怎么称呼它实际上并没有什么区别。如果你正处于“不知道如何创建一个新文件”的阶段,请寻求基本的Xcode教程。)
1.把上面所有的代码放到文件中
1.您刚刚向项目中添加了一个类“RoundedCornersAndTrueBorder”。
1.在你的故事板上。将 * 普通UIView添加到场景 *。事实上,让它实际上任何大小/形状,任何你喜欢的。
1.看看“身份检查员”。(如果你不知道这是什么,请寻求基本教程。)只需将类更改为“RoundedCornersAndTrueBorder”。(一旦你开始输入“Roun。..”,它会猜出你指的是哪个类。
1.你完成了-运行项目。
请注意,当然,您必须向UIView添加完整和正确的约束,就像您在Xcode中所做的任何事情一样。好好享受吧
类似的解决方案:
https://stackoverflow.com/a/57465440/294884-图像+圆角+阴影
https://stackoverflow.com/a/41553784/294884-两角点问题
https://stackoverflow.com/a/59092828/294884-“阴影+洞”或“发光盒”问题
https://stackoverflow.com/a/57400842/294884-“边界和间隙”问题
https://stackoverflow.com/a/57514286/294884-基本“添加”边框
也请看下面的备选答案!:)

zf9nrax1

zf9nrax13#

绝对完美的2019解决方案

言归正传,下面就是你要做的。
1.实际上不要使用视图附带的“基本”图层
1.创建一个新的图层 * 仅用于 * 图像。你现在可以遮罩这个(循环)而不影响下一层
1.为边界创建一个新层。它将安全地不被图片层掩蔽。
关键事实是
1.使用CALayer,您确实可以应用。遮罩***且只影响该层***
1.当画一个圆(或实际上任何边界)时,必须非常小心地注意到你只能得到“一半宽度”的事实-简而言之,永远不要使用你所画的相同路径来裁剪。
1.请注意,原始的猫图像与水平黄色箭头的宽度完全相同。您必须小心绘制图像,以便***整个***图像出现在圆角中,***小于***整个自定义控件。
那么,按常规方式设置

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标准控件一样完美:

一切看不见的东西都是看不见的;您可以透视整个自定义控件后面的任何材质,没有“半厚”的问题或缺少图像材质,您可以按通常的方式设置自定义控件背景颜色等。检查员正确地控制所有工作。(呼!)
类似的解决方案:
https://stackoverflow.com/a/57465440/294884-图像+圆角+阴影
https://stackoverflow.com/a/41553784/294884-两角点问题
https://stackoverflow.com/a/59092828/294884-“阴影+洞”或“发光盒”问题
https://stackoverflow.com/a/57400842/294884-“边界和间隙”问题
https://stackoverflow.com/a/57514286/294884-基本“添加”边框

2ekbmq32

2ekbmq324#

**use this extension for round borders and corner**


       
       
    
 extension UIView {

   func roundCorners(corners: UIRectCorner, radius: CGFloat) {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        layer.mask = mask
    }
    
    func roundCornersWithBorder(corners: UIRectCorner, radius: CGFloat) {
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = UIBezierPath(roundedRect: bounds, byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: radius, height: radius)).cgPath
        
        layer.mask = maskLayer
        
        // Add border
        let borderLayer = CAShapeLayer()
        borderLayer.path = maskLayer.path // Reuse the Bezier path
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = UIColor(red:3/255, green:33/255, blue:70/255, alpha: 0.15).cgColor
        borderLayer.lineWidth = 2
        borderLayer.frame = bounds
        layer.addSublayer(borderLayer)
    }
    
}

像这样使用
myView.roundCornersWithBorder(角:[.topLeft,.topRight],半径:8.0)
myView.roundCorners(角:[.topLeft,.topRight],半径:8.0)

5cnsuln7

5cnsuln75#

当然有!每个视图都有一个layer属性(您可以通过给图层添加圆角来了解这一点)。layer上的另外两个属性是borderColorborderWidth。只需设置这些,您就可以为视图添加边框!(边框将遵循圆角。)确保对borderColor使用UIColor.CGColor,因为普通的UIColor将不匹配类型。

相关问题