为什么 .trim() 和 .lines() 修饰符的行为会根据其顺序在 Circle 上发生变化?

Hec*_*rdo 0 swiftui

我无法理解为什么 SwiftUI 修饰符.stroke().trim()Circle 上的行为有所不同,具体取决于您首先放置的内容。

在 Xcode Playground 中,以下代码:

struct ContentView: View {
  var body: some View {
    Circle()
      .trim(from: 0, to: 0.5)
  }
}

PlaygroundPage.current.setLiveView(
  ContentView()
    .padding(10)
    .frame(width: 100, height: 100)
)
Run Code Online (Sandbox Code Playgroud)

结果(如预期)如下图所示:

在此输入图像描述

如果我只想要半圆的轮廓,我添加.stroke修改器:

Circle()
  .trim(from: 0, to: 0.5)
  .stroke() // add this
Run Code Online (Sandbox Code Playgroud)

结果是:

在此输入图像描述

到目前为止,一切都很好。但如果我向后做,它就不会按预期工作。也就是说,如果我首先构建一个圆的轮廓:

Circle()
  .stroke()
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

然后我尝试通过这样做来获得轮廓的下半部分:

Circle()
  .stroke()
  .trim(from: 0, to: 0.5)
Run Code Online (Sandbox Code Playgroud)

我得到一个(意外的)结果:

在此输入图像描述

不仅如此:如果我更改toto的参数,我会得到以下结果:trim0.25

Circle()
  .stroke()
  .trim(from: 0, to: 0.25)
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

我知道,一般来说,SwiftUI 修饰符的顺序很重要,但在这种情况下,我不明白我提供的代码片段的结果。

rob*_*off 6

您调用的方法stroke实现相当短。其实现本质上是这样的:

\n
struct _StrokedShape<S: Shape>: Shape {\n    var s: S\n    var style: StrokeStyle\n\n    func path(in rect: CGRect) -> Path {\n        return s\n            .path(in: rect)\n            .strokedPath(style)\n            // strokedPath is defined on Path.\n    }\n}\n\nextension Shape {\n    func stroke(lineWidth: CGFloat = 1) -> some Shape {\n        return _StrokedShape(s: self, style: .init(lineWidth: lineWidth))\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

值得注意的是,抚摸路径总是会产生闭合路径。

\n

trim方法有一个类似的简短实现,本质上是这样的:

\n
struct _TrimmedShape<S: Shape>: Shape {\n    var s: S\n    var from: CGFloat\n    var to: CGFloat\n\n    func path(in rect: CGRect) -> Path {\n        return s\n            .path(in: rect)\n            .trimmedPath(from: from, to: to)\n            // trimmedPath is defined on Path.\n    }\n}\n\nextension Shape {\n    func trim(from: CGFloat, to: CGFloat) -> some Shape {\n        return _TrimmedShape(s: self, from: from, to: to)\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

值得注意的是,修剪路径通常会产生开放路径。

\n

(我在调试器中逐步完成了这两种方法的实现,以验证它们是否是这样工作的。)

\n

现在我们了解了 SwiftUI 如何实现这些修饰符。另一件需要理解的事情是 SwiftUI 如何绘制Shape:它调用Shape\ 的path(in:)方法,然后用前景色(或者,在较旧的操作系统上,前景色)填充路径。如果路径打开,则填充后会自动关闭。默认的前景样式是纯黑色或纯白色(取决于配色方案)。

\n

有了这些知识,我们就可以理解您所看到的结果。

\n

您的第一个测试是这段代码:

\n
    Circle()\n      .trim(from: 0, to: 0.5)\n
Run Code Online (Sandbox Code Playgroud)\n

产生这个开放的半圆路径:

\n

开半圆

\n

当填充时,它会闭合以形成闭合的半圆路径:

\n

封闭的半圆

\n

您的第二个测试是这段代码:

\n
Circle()\n  .trim(from: 0, to: 0.5)\n  .stroke() // add this\n
Run Code Online (Sandbox Code Playgroud)\n

默认笔画lineWidth为 1,这使得生成的路径难以理解,因此我将改用以下代码:

\n
Circle()\n  .trim(from: 0, to: 0.5)\n  .stroke(lineWidth: 5)\n
Run Code Online (Sandbox Code Playgroud)\n

trim这会描画由上面的开放半圆 \xe2\x80\x94产生的路径,产生这个闭合的半环形路径:

\n

闭合半环形路径

\n

填充该闭合半环形路径绘制一个半圆。

\n

您的第三个测试是这段代码:

\n
Circle()\n  .stroke()\n
Run Code Online (Sandbox Code Playgroud)\n

同样,默认值lineWidth1 会使结果难以理解,因此我将改用以下代码:

\n
Circle()\n  .stroke(lineWidth: 5)\n
Run Code Online (Sandbox Code Playgroud)\n

这会绘制一个圆,生成一条包含两个闭合圆形子路径的路径路径:

\n

两条同心闭合圆形路径

\n

从上图中无法看出,但子路径的运行方向相反,因此当使用非零缠绕规则填充时填充时,它看起来像一个圆圈。

\n

您的第四个测试是这段代码:

\n
Circle()\n  .stroke()\n  .trim(from: 0, to: 0.5)\n
Run Code Online (Sandbox Code Playgroud)\n

我将再次使用lineWidth5 来代替:

\n
Circle()\n  .stroke(lineWidth: 5)\n  .trim(from: 0, to: 0.5)\n
Run Code Online (Sandbox Code Playgroud)\n

请记住,stroke生成的路径包含两个。如果这些圆具有完全相同的半径,则修剪掉路径的后半部分将恰好修剪掉其中一个圆。但其中一个圆圈比另一个圆圈稍大,并且较小的圆圈位于路径中较早的位置。因此它会修剪掉大圆的大部分,但不是全部。它生成一个包含闭合圆形子路径和微小开弧子路径的路径:

\n

圆形子路径和小圆弧子路径

\n

当填充时,它会关闭包含闭合圆形子路径和微小闭合圆弧段子路径的路径:

\n

圆形子路径和小圆段子路径

\n

当我们把圆变lineWidth小时,圆的大小就会变得更接近。更多的大圆被修剪掉,剩余的弧/线段移近更小的圆。lineWidth当我们在 1 和 5 之间变化时会发生以下情况:

\n

圆弧缩小并消失在圆中

\n

当 alineWidth为 1 时,圆弧段非常细,填充时不会产生明显的颜色,这就是为什么您在图像中根本看不到它。

\n

您的第五次也是最后一次测试是以下代码:

\n
Circle()\n  .stroke()\n  .trim(from: 0, to: 0.25)\n
Run Code Online (Sandbox Code Playgroud)\n

像往常一样,我将使用lineWidth5 来代替:

\n
Circle()\n  .stroke(lineWidth: 5)\n  .trim(from: 0, to: 0.25)\n
Run Code Online (Sandbox Code Playgroud)\n

它产生这个开放的近半圆形路径:

\n

略大于半圆

\n

由于我们要修剪掉描边路径的最后 3/4,因此我们修剪掉了整个大圆和几乎一半的小圆。填充后,它会关闭这条路径:

\n

略大于半圆,闭合

\n

1lineWidth会使圆的大小更加接近,因此您可以修剪掉所有较大的圆和几乎恰好一半的较小圆。但如果放大图像,您会发现形状的顶部边缘确实有一个小角度,因为它不是完美的半圆:

\n

问题第五张图片的缩放裁剪顶部边缘

\n