UIBezierPath 椭圆形问题,线条突出

问题描述 投票:0回答:1

我正在尝试使用 UIBezierPath 创建椭圆形/圆角矩形。我想要达到的就是这个形状

问题之一是我希望能够找到正确的半径来存档我的目标形状,我遇到的第二个问题是我可以看到线条伸出,代码不会产生干净的形状

override func layoutSubviews() {
    super.layoutSubviews() 
    
    layer.sublayers?.forEach { $0.removeFromSuperlayer() }

    let path = makePath()
    path.lineJoinStyle = .round
    path.lineCapStyle = .round
    
    let shapeLayer = CAShapeLayer()
    // Enable antialiasing
    shapeLayer.shouldRasterize = true
    shapeLayer.rasterizationScale = UIScreen.main.scale
    
    shapeLayer.lineJoin = .round
    shapeLayer.path = path.cgPath
    //shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = strokeColor.cgColor
    shapeLayer.lineWidth = lineWidth
    shapeLayer.lineCap = .round
    
    
    layer.addSublayer(shapeLayer)
    layer.backgroundColor = overlayColor.cgColor
    
    //backgroundPath is the blur overlay
    let backgroundPath = UIBezierPath(rect: bounds)
    backgroundPath.lineJoinStyle = .round
    backgroundPath.lineCapStyle = .round

    let maskLayer = CAShapeLayer()
    // Enable antialiasing
    maskLayer.shouldRasterize = true
    maskLayer.rasterizationScale = UIScreen.main.scale
    
    maskLayer.frame = bounds
    maskLayer.lineJoin = .round

    //backgroundPath.append(path)
    maskLayer.fillRule = .evenOdd
    maskLayer.path = backgroundPath.cgPath
    layer.mask = maskLayer

    addAdditionalLayersIfNeeded(rect)
}


override func makePath(rect: CGRect) -> UIBezierPath {
    UIBezierPath(roundedRect: preferedSize, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: preferedSize.width * 0.33, height: preferedSize.height * 0.33))
}

我用来创建形状的尺寸

preferedSize: CGRect(x: 32, y: 278, width: 320, height: 400)

这就是代码将呈现的内容

swift uibezierpath
1个回答
0
投票

您遇到了一些错误

UIBezierPath(roundedRect: ...

首先,忽略

height
cornerRadii
值。所以,我们也忽略它并使用:

var cr: CGFloat = 0.25

let r: CGRect = CGRect(x: 20.0, y: 20.0, width: 320.0, height: 400.0)
let path = UIBezierPath(roundedRect: r, cornerRadius: cr * r.width)
shapeLayer.path = path.cgPath

基本

CAShapeLayer
包括:

  • .lineWidth = 20.0
  • .strokeColor = UIColor.orange.cgColor
  • .fillColor = UIColor.cyan.cgColor
  • 查看背景颜色黄色

因此,我们从 25% 的角半径开始,然后逐渐增加它:

如您所见,当我们到达

29.5%
的角半径时,实际半径跳跃并且我们得到一个奇怪的“间隙” - 这也留下了一个小的未对准,这就是您在侧面看到的“凹凸”。

随着我们不断增加宽度百分比半径值,实际半径保持不变,直到达到

36.5%
——此时半径发生变化并且未对准消失(我们得到平滑的边缘)。

请注意,这将根据路径的实际大小和笔划的宽度而有所不同。

如果我们这样做:

print(path.cgPath
(25%),我们会在调试控制台中得到这个:

  moveto (154.828, 20)
    lineto (205.172, 20)
    curveto (243.995, 20) (263.407, 20) (284.302, 26.6072)
    lineto (284.302, 26.6072)
    curveto (307.117, 34.9111) (325.089, 52.8831) (333.393, 75.6978)
    curveto (340, 96.5935) (340, 116.005) (340, 154.828)
    lineto (340, 285.172)
    curveto (340, 323.995) (340, 343.407) (333.393, 364.302)
    lineto (333.393, 364.302)
    curveto (325.089, 387.117) (307.117, 405.089) (284.302, 413.393)
    curveto (263.407, 420) (243.995, 420) (205.172, 420)
    lineto (154.828, 420)
    curveto (116.005, 420) (96.5935, 420) (75.6978, 413.393)
    lineto (75.6978, 413.393)
    curveto (52.8831, 405.089) (34.9111, 387.117) (26.6072, 364.302)
    curveto (20, 343.407) (20, 323.995) (20, 285.172)
    lineto (20, 154.828)
    curveto (20, 116.005) (20, 96.5935) (26.6072, 75.6978)
    lineto (26.6072, 75.6978)
    curveto (34.9111, 52.8831) (52.8831, 34.9111) (75.6978, 26.6072)
    curveto (96.5935, 20) (116.005, 20) (154.828, 20)
    lineto (154.828, 20)

正如我们所见,“圆角矩形”路径实际上是一系列直线到和曲线到指令。

我的猜测是苹果的内部

roundedRect
算法遇到了浮点错误。

避免错误的一种方法是使用此扩展自己构建路径:

extension UIBezierPath {
    static func roundedRect(rect: CGRect, cornerRadius: CGFloat) -> UIBezierPath {
        // use shorter of width or height as max corner radius value
        //  and don't exceed 50%
        let v: CGFloat = min(rect.width, rect.height)
        let cr: CGFloat = min(v * 0.5, cornerRadius)
        let path = CGMutablePath()
        let start = CGPoint(x: rect.midX, y: rect.minY)
        path.move(to: start)
        path.addArc(tangent1End: rect.topRight, tangent2End: rect.bottomRight, radius: cr)
        path.addArc(tangent1End: rect.bottomRight, tangent2End: rect.bottomLeft, radius: cr)
        path.addArc(tangent1End: rect.bottomLeft, tangent2End: rect.topLeft, radius: cr)
        path.addArc(tangent1End: rect.topLeft, tangent2End: start, radius: cr)
        path.closeSubpath()
        return UIBezierPath(cgPath: path)
    }
}

// uses this "convenience" extension
extension CGRect {
    var topRight: CGPoint { CGPoint(x: maxX, y: minY) }
    var topLeft: CGPoint { CGPoint(x: minX, y: minY) }
    var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
    var bottomLeft: CGPoint { CGPoint(x: minX, y: maxY) }
}

现在,宽度为 33% 的圆角半径路径生成如下:

var cr: CGFloat = 0.33

let r: CGRect = CGRect(x: 20.0, y: 20.0, width: 320.0, height: 400.0)
let path = UIBezierPath(roundedRect: r, cornerRadius: cr * r.width)
shapeLayer.path = path.cgPath

给我们这个:

值得注意的是:苹果的算法生成的是“连续曲线”圆角,略有不同。

此扩展:

extension UIBezierPath {
    static func roundedRect(
        rect: CGRect,
        corners: UIRectCorner = .allCorners,
        cornerRadius: CGFloat
    ) -> UIBezierPath {
        // use shorter of width or height as max corner radius value
        //  and don't exceed 50%
        let v: CGFloat = min(rect.width, rect.height)
        let cr: CGFloat = min(v * 0.5, cornerRadius)
        let tweak: CGFloat = 1.2 // could experiment with this
        let offset = cr * tweak
        if rect.width > 2 * offset { // less than that, my algorithm starts to break down — but theirs works!
            let path = CGMutablePath()
            let start = CGPoint(x: rect.midX, y: rect.minY)
            path.move(to: start)
            if corners.contains(.topRight) {
                path.addLine(to: rect.topRight.offset(x: -offset, y: 0))
                path.addQuadCurve(to: rect.topRight.offset(x: 0, y: offset), control: rect.topRight)
            } else {
                path.addLine(to: rect.topRight)
            }
            if corners.contains(.bottomRight) {
                path.addLine(to: rect.bottomRight.offset(x: 0, y: -offset))
                path.addQuadCurve(to: rect.bottomRight.offset(x: -offset, y: 0), control: rect.bottomRight)
            } else {
                path.addLine(to: rect.bottomRight)
            }
            if corners.contains(.bottomLeft) {
                path.addLine(to: rect.bottomLeft.offset(x: offset, y: 0))
                path.addQuadCurve(to: rect.bottomLeft.offset(x: -0, y: -offset), control: rect.bottomLeft)
            } else {
                path.addLine(to: rect.bottomLeft)
            }
            if corners.contains(.topLeft) {
                path.addLine(to: rect.topLeft.offset(x: 0, y: offset))
                path.addQuadCurve(to: rect.topLeft.offset(x: offset, y: 0), control: rect.topLeft)
            } else {
                path.addLine(to: rect.topLeft)
            }
            path.closeSubpath()
            return UIBezierPath(cgPath: path)
        }
        
        return UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: cornerRadius, height: cornerRadius))
    }
}

// uses these "convenience" extensions
extension CGRect {
    var topRight: CGPoint { CGPoint(x: maxX, y: minY) }
    var topLeft: CGPoint { CGPoint(x: minX, y: minY) }
    var bottomRight: CGPoint { CGPoint(x: maxX, y: maxY) }
    var bottomLeft: CGPoint { CGPoint(x: minX, y: maxY) }
}

extension CGPoint {
    func offset(x xOffset: CGFloat, y yOffset: CGFloat) -> CGPoint {
        CGPoint(x: x + xOffset, y: y + yOffset)
    }
}

给出这个结果:

请注意:这些扩展是此处讨论中略有修改的版本UIBezierPath bezierPathWithRoundedRect:cornerRadius值不一致

© www.soinside.com 2019 - 2024. All rights reserved.