如何绘制混合了圆角和尖角的不规则多边形?

How do I draw irregular polygons with a mix of rounded and sharp corners?

我希望能够绘制混合了锐角和钝角的不规则多边形,有些角是圆的,有些不是。

假设我有一个这样的多边形:

我想把它画成圆角:

或者每隔一个角变圆:

我该怎么做?

David Rönnqvist 写了一篇很棒的文章,very informative article 描述了绘制圆角背后的数学原理,但它非常复杂,如果您对三角函数和几何学不满意,会导致您的大脑爆炸。

在同一个帖子 Anjali posted an answer 中,展示了如何使用 CGMutablePath 方法 addArc(tangent1End:tangent2End:radius:transform:)

但是,它没有告诉我如何处理顶点数可变的多边形,或者如何混合圆角和尖角。我该怎么做?

在不让自己发疯的情况下做到这一点的关键是方法 addArc(tangent1End:tangent2End:radius:transform:)。这会在现有的 CGMutablePath 中添加一条弧线。该方法从路径的当前点开始。您指定一个点 tangent1End,这是您要为其绘制圆角的顶点,以及另一个点 tangent2End,它是 下一个 顶点你的 pologon.

要绘制具有可变顶点数的多边形,我们使用点数组。

为了使所有的角变圆,您必须将路径的起点设置为多边形直线段之一上的某个点,然后 return 到终点的那个点。计算2个点的中点很简单:

let midpoint = CGPoint(x: (point1.x + point2.x)/2, y: (point1.y + point2.y)/2 )

因此我们将路径的当前点移动到顶点数组中第一个点和最后一个点之间的中点作为起点:

let midpoint = CGPoint(x: (first.point.x + last.point.x) / 2, y: (first.point.y + last.point.y) / 2 )
path.move(to: midpoint)

然后,对于多边形中的每个顶点,我们要么只画一条线到该点(对于尖角),要么使用美妙、易于使用的 addArc(tangent1End:tangent2End:radius:transform:) 画一条线段,以圆弧结束,围绕该顶点。 我将创建一个 UIView 的子class,RoundedCornerPolygonView。

class RoundedCornerPolygonView: UIView {
}

它将自己设置为使其内容层成为 CAShapeLayer。为此,您只需将 class var layerClass 添加到您的自定义 UIView subclass:

// This class var causes the view's base layer to be a CAShapeLayer.
class override var layerClass: AnyClass {
    return CAShapeLayer.self
}

为了跟踪点数组,以及哪些点应该是圆的,哪些应该是平滑的,我们将定义一个结构 PolygonPoint:

struct PolygonPoint {
    let point: CGPoint
    let isRounded: Bool
}

我们将给 class 一个 PolygonPoint 数组:

public var points = [PolygonPoint]()

并添加一个 didSet 方法来更新我们的形状图层的路径,如果它发生变化:

public var points = [PolygonPoint]() {
    didSet {
        guard points.count >= 3 else {
            print("Polygons must have at least 3 sides.")
            return
        }
        buildPolygon()
    }
}

下面是从上面的 PolygonPoints 数组构建多边形路径的代码:

/// Rebuild our polygon's path and install it into our shape layer.
private func buildPolygon() {
    guard points.count >= 3 else { return }
    drawPoints() // Draw each vertex into another layer if requested.
    let first = points.first!
    let last = points.last!

    let path = CGMutablePath()

    // Start at the midpoint between the first and last vertex in our polygon
    // (Since that will always be in the middle of a straight line segment.)
    let midpoint = CGPoint(x: (first.point.x + last.point.x) / 2, y: (first.point.y + last.point.y) / 2 )
    path.move(to: midpoint)

    //Loop through the points in our polygon.
    for (index, point) in points.enumerated() {
        // If this vertex is not rounded, just draw a line to it.
        if !point.isRounded {
            path.addLine(to: point.point)
        } else {
            //Draw an arc from the previous vertex (the current point), around this vertex, and pointing to the next vertex.
            let nextIndex = (index+1) % points.count
            let nextPoint = points[nextIndex]
            path.addArc(tangent1End: point.point, tangent2End: nextPoint.point, radius: cornerRadius)
        }
    }

    // Close the path by drawing a line from the last vertex/corner to the midpoint between the last and first point
    path.addLine(to: midpoint)

    // install the path into our (shape) layer
    let layer = self.layer as! CAShapeLayer
    layer.path = path
}

我在 Github 上创建了一个示例项目,它实现了上面定义的 RoundedCornerPolygonView class,并让您 select 哪些角应该圆滑或平滑运行时间。

该项目名为“RoundedCornerPolygon”。 (link)

它的视图控制器 class 中也有从顶点数组 (CGPoints) 开始的代码。它为顶点数组中的每个顶点填充一个垂直堆栈视图,并为该顶点加上一个标签。然后它构建一个 PolygonPoint 数组并将其安装到 RoundedCornerPolygonView.

如果用户切换任何开关,它会重建 PolygonPoint 的数组并将它们传递给 RoundedCornerPolygonView,它会重新绘制自己。 屏幕看起来像这样: