iOS -检测路径低于另一个路径[关闭]

w8f9ii69  于 2023-06-25  发布在  iOS
关注(0)|答案(1)|浏览(101)

已关闭,此问题需要更focused。目前不接受答复。
**想改善这个问题吗?**更新问题,使其仅通过editing this post关注一个问题。

3天前关闭。
Improve this question
这是我的要求。它基本上是一个橡皮擦概念。我有n边的多边形。所有行的(x,y)存储在模型中。要擦除多边形中的任何部分,用户可以在多边形上绘制线,如果线完全在徒手绘制的覆盖范围内,则必须突出显示特定的线,然后可以擦除。
上下文绘制用于绘制多边形和橡皮擦。根据模型中的线数据,绘制多边形。当用户在橡皮擦模式下绘制时,它以浅蓝色的较大线宽描边。当图形完全覆盖多边形中的任何线时,该线可以用深蓝色高亮显示,表示选择。

问题是,我不能确定这条线是否完全覆盖了橡皮擦。因为我只有一条线的起点和终点,所以很难确定它。当用户在橡皮擦模式下绘制时,什么是可靠的方法来识别线条?

编辑尝试为评论

中的问题提供答案

对于第一个问题,底部直线必须被识别并删除,因为它被完全覆盖。为了回答第二种情况,不会选择/识别任何红线。要回答第三个问题,必须选择完全在蓝色笔划覆盖范围内的所有线-以绿色突出显示。

5f0d552i

5f0d552i1#

我猜你想要这样的东西:

我将完全“擦除”的线变成虚线,而不是完全删除它们。

**如果您的部署目标至少是iOS 16,**那么您可以使用lineSubtracting method of CGPath来完成“繁重的工作”。

Apple还没有在网站上提供这种方法的真实的文档,但头文件描述如下:
返回一个新路径,其中包含一条来自此路径的线,该线不与给定路径的填充区域重叠。

  • 产品参数:
  • 其他:减法的路径。
  • 规则:用于确定哪些区域被视为other内部的规则。如果未指定,则默认为CGPathFillRule.winding规则。
  • 返回:新路径。

所得路径的线是此路径的不与other的填充区域重叠的线。
裁剪的相交子路径创建开放子路径。与other不相交的闭合子路径保持闭合状态。
所以,我们的策略是:

  • 为其中一条直线段创建CGPath
  • 创建用户擦除手势的CGPath
  • 笔划擦除路径。
  • 在直线段路径上使用lineSubtracting,经过描边擦除路径,以获得仅包含直线段中未被擦除器覆盖的部分的路径。
  • 如果lineSubtracting返回的路径为空,则直线已被完全擦除。
  • 对每条直线段重复上述步骤。

我们来试试首先,我将编写一个模型类型来存储原始路径和擦除后保留的路径部分:

struct ErasablePath {
    var original: Path
    var remaining: Path
}

让我们添加两个额外的初始化器,以及一个通过减去一个(描边的)橡皮擦路径来更新remaining路径的方法:

extension ErasablePath {
    init(_ path: Path) {
        self.init(original: path, remaining: path)
    }
    
    init(start: CGPoint, end: CGPoint) {
        self.init(Path {
            $0.move(to: start)
            $0.addLine(to: end)
        })
    }
    
    func erasing(_ eraserPath: CGPath) -> Self {
        return Self(
            original: original,
            remaining: Path(remaining.cgPath.lineSubtracting(eraserPath))
        )
    }
}

我将使用以下函数将一个点数组转换为一个ErasablePath s数组:

func makeErasableLines(points: [CGPoint]) -> [ErasablePath] {
    guard let first = points.first, let last = points.dropFirst().last else {
        return []
    }
    return zip(points, points.dropFirst()).map {
        ErasablePath(start: $0, end: $1)
    } + [ErasablePath(start: last, end: first)]
}

以下是玩具应用程序的完整数据模型:

struct Model {
    var erasables: [ErasablePath] = makeErasableLines(points: [
        CGPoint(x: 50, y: 100),
        CGPoint(x: 300, y: 100),
        CGPoint(x: 300, y: 400),
        CGPoint(x: 175, y: 400),
        CGPoint(x: 175, y: 250),
        CGPoint(x: 50, y: 250),
    ])

    var eraserPath: Path = Path()

    var strokedEraserPath: Path = Path()

    var isErasing: Bool = false

    let lineWidth: CGFloat = 44
}

为了在用户与应用交互时更新模型,我需要一些方法来响应用户开始、移动和结束触摸,以及一种重置数据模型的方法:

extension Model {
    mutating func startErasing(at point: CGPoint) {
        eraserPath.move(to: point)
        isErasing = true
    }
    
    mutating func continueErasing(to point: CGPoint) {
        eraserPath.addLine(to: point)
        strokedEraserPath = eraserPath.strokedPath(.init(
            lineWidth: 44,
            lineCap: .round,
            lineJoin: .round
        ))
        let cgEraserPath = strokedEraserPath.cgPath
        erasables = erasables
            .map { $0.erasing(cgEraserPath) }
    }
    
    mutating func endErasing() {
        isErasing = false
    }
    
    mutating func reset() {
        self = .init()
    }
}

我们需要一个视图来绘制可擦除路径和擦除路径。我会用绿色绘制每个原始可擦除路径,如果它已被完全擦除,则绘制虚线。我将用红色绘制每条可擦除路径的剩余(未擦除)部分。我会用半透明的紫色画出描边的橡皮擦路径。

struct DrawingView: View {
    @Binding var model: Model

    var body: some View {
        Canvas { gc, size in
            for erasable in model.erasables {
                gc.stroke(
                    erasable.original,
                    with: .color(.green),
                    style: .init(
                        lineWidth: 2,
                        lineCap: .round,
                        lineJoin: .round,
                        miterLimit: 1,
                        dash: erasable.remaining.isEmpty ? [8, 8] : [],
                        dashPhase: 4
                    )
                )
            }

            for erasable in model.erasables {
                gc.stroke(
                    erasable.remaining,
                    with: .color(.red),
                    lineWidth: 2
                )
            }

            gc.fill(
                model.strokedEraserPath,
                with: .color(.purple.opacity(0.5))
            )
        }
    }
}

在我的ContentView中,我将在绘图视图中添加一个DragGesture,并显示一个重置按钮:

struct ContentView: View {
    @Binding var model: Model
    
    var body: some View {
        VStack {
            DrawingView(model: $model)
                .gesture(eraseGesture)

            Button("Reset") { model.reset() }
                .padding()
        }
    }
    
    var eraseGesture: some Gesture {
        DragGesture(minimumDistance: 0, coordinateSpace: .local)
            .onChanged { drag in
                if model.isErasing {
                    model.continueErasing(to: drag.location)
                } else {
                    model.startErasing(at: drag.location)
                }
            }
            .onEnded { drag in
                model.endErasing()
            }
    }
}

这是我用来生成答案顶部动画的代码。但我承认那是个被操纵的演示。lineSubtracting方法有一点bug,我小心地避免触发bug。下面是bug:

如果ErasablePath是一条水平线段,而擦除器路径从该段下方开始,则lineSubtracting将删除整个可擦除路径,即使擦除器路径和线段没有重叠!
为了解决这个bug,我将以下init方法插入到Model中:

struct Model {
    ... existing code ...

    init() {
        // lineSubtracting has a bug (still present in iOS 17.0 beta 1):
        // If the receiver is a horizontal line, and the argument (this eraserPath) starts below that line, the entire receiver is removed, even if the argument doesn't intersect the receiver at all.
        // To work around the bug, I put a zero-length segment at the beginning of eraserPath way above the actual touchable area.
        startErasing(at: .init(x: -1000, y: -1000))
        continueErasing(to: .init(x: -1000, y: -1000))
        endErasing()
    }
}

橡皮擦路径总是从可擦除路径的上方开始,因此它不再触发错误:

相关问题