SwiftUI:自定义 Shape 使用 InsettableShape 协议实现向内绘制边框

2020-05-08 01:00:31 浏览数 (1)

如果创建的形状没有特定大小,它将自动扩展以占据所有可用空间。例如,这将创建一个填充我们视图的圆,并为其提供40点蓝色边框:

代码语言:swift复制
struct ContentView: View {
    var body: some View {
        Circle()
            .stroke(Color.blue, lineWidth: 40)
    }
}
stroke() 绘制stroke() 绘制
仔细观察边框的左右边缘——您注意到边框是怎么被切掉的吗?

您在这里看到的是SwiftUI在形状周围绘制边框的方式的副作用。如果您递给某人一个圆的铅笔轮廓,并要求他们用粗笔在该圆上画线,他们将绘制出该圆的精确线——大约一半的笔在该线的内部,一半在该线的外部。这就是SwiftUI为我们所做的,但是当形状到达屏幕边缘时,则意味着边框的外部最终超出了屏幕边缘。

现在尝试改用这个圆:

代码语言:swift复制
Circle()
    .strokeBorder(Color.blue, lineWidth: 40)
strokeBorder() 绘制strokeBorder() 绘制

这将stroke()更改为strokeBorder(),现在我们得到了更好的结果:我们的所有边框都是可见的,因为Swift在圆的内部绘制而不是将圆作为绘制的中心。

之前我们建立了一个如下的弧形:

代码语言:swift复制
struct Arc: Shape {
    var startAngle: Angle
    var endAngle: Angle
    var clockwise: Bool

    func path(in rect: CGRect) -> Path {
        let rotationAdjustment = Angle.degrees(90)
        let modifiedStart = startAngle - rotationAdjustment
        let modifiedEnd = endAngle - rotationAdjustment

        var path = Path()
        path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockwise)

        return path
    }
}

就像Circle一样,它会自动占用所有可用空间。但是,这种代码不起作用:

代码语言:swift复制
Arc(startAngle: .degrees(-90), endAngle: .degrees(90), clockwise: true)
    .strokeBorder(Color.blue, lineWidth: 40)

如果您打开Xcode的错误消息,则会看到它显示“Value of type 'Arc' has no member 'strokeBorder'''——也就是说,arc中不存在strokeBorder()修饰符。

SwiftUI的Circle和我们的Arc之间有一个微小但重要的区别:两者均符合Shape协议,但Circle也符合名为InsettableShape的第二种协议。该形状可以嵌入(向内减小)一定距离以产生另一个形状。它产生的插图形状可以是任何其他类型的插图形状,但实际上,它应该是一个有相同形状的较小的矩形。

为了使Arc符合InsettableShape,我们需要为其添加一个额外的方法:inset(by :)。这将获得插入量(笔画的线宽的一半),并应返回一种新的可插入形状——在我们的实例中,这意味着我们应该创建一个插入弧型。问题是我们不知道圆弧的实际大小,因为尚未调用path(in :)

事实证明,解决方案非常简单:如果我们为Arc形状提供一个默认为0的新insetAmount属性,则只要调用inset(by :)就可以添加该属性。添加到inset允许我们在需要时多次调用inset(by :),例如,如果我们想手动调用一次,则使用strokeBorder()

首先,将此新属性添加到Arc

代码语言:swift复制
var insetAmount: CGFloat = 0

现在给它这个inset(by :)方法:

代码语言:swift复制
func inset(by amount: CGFloat) -> some InsettableShape {
    var arc = self
    arc.insetAmount  = amount
    return arc
}

传入的数量参数应应用于所有边缘,这在圆弧的情况下意味着我们应使用它减小绘制半径。因此,将path(in :)内部的addArc()调用更改为:

代码语言:swift复制
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: rect.width / 2 - insetAmount, startAngle: modifiedStart, endAngle: modifiedEnd, clockwise: !clockwise)

通过该更改,我们现在可以使Arc符合InsettableShape,如下所示:

代码语言:swift复制
struct Arc: InsettableShape {
自定义 Arc 使用 strokeBorder() 绘制自定义 Arc 使用 strokeBorder() 绘制
注意:InsettableShape实际上是基于Shape构建的,因此无需在其中添加两者。

译自 Adding strokeBorder() support with InsettableShape

0 人点赞