You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DragGesture.onChange循环致CPU过载应用卡顿,求实时优化方案

SwiftUI点绘应用实时相交检测性能优化方案

核心问题分析

在DragGesture().onChanged中执行嵌套循环的相交检测,每次拖拽事件都会遍历当前绘制的所有线段与已绘制的所有线段——随着线条长度增加,计算量呈指数级增长,直接导致CPU过载和绘制延迟。

优化方案

  • 只检测最新线段:每次拖拽仅新增一段线段(当前点和上一个点),无需遍历当前绘制的所有历史线段,大幅减少循环次数。
  • 检测到相交立即终止:一旦发现相交,立刻清空当前绘制的线条,停止后续不必要的计算。
  • 合并冗余数据结构:将Drawing1和Drawing2合并为通用的Drawing结构体,简化代码逻辑。
  • 边界框预检测:先判断两条线段的边界框是否重叠,不重叠的直接跳过精确相交计算,进一步减少计算量。

修改后的代码

import SwiftUI
import CoreGraphics

// 合并为通用的Drawing结构体,消除冗余
struct Drawing {
    var points: [CGPoint] = []
}

struct ContentView: View {
    @State private var currentOrangeDrawing: Drawing = Drawing()
    @State private var orangeDrawings: [Drawing] = []
    @State private var currentBlackDrawing: Drawing = Drawing()
    @State private var blackDrawings: [Drawing] = []
    
    var body: some View {
        GeometryReader { geo in
            ZStack {
                // 绘制橙色线条
                Path { path in
                    for drawing in orangeDrawings {
                        add(drawing: drawing, toPath: &path)
                    }
                    add(drawing: currentOrangeDrawing, toPath: &path)
                }
                .stroke(Color.orange, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
                
                // 绘制黑色线条
                Path { path in
                    for drawing in blackDrawings {
                        add(drawing: drawing, toPath: &path)
                    }
                    add(drawing: currentBlackDrawing, toPath: &path)
                }
                .stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .round, lineJoin: .round))
                
                // 橙色绘制触发区域
                Image("example1")
                    .resizable(resizingMode: .stretch)
                    .scaledToFit()
                    .clipped()
                    .position(x: 200, y: 200)
                    .frame(width: 100, height: 100)
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                currentOrangeDrawing.points.append(value.location)
                            }
                            .onEnded { _ in
                                orangeDrawings.append(currentOrangeDrawing)
                                currentOrangeDrawing = Drawing()
                            }
                    )
                
                // 黑色绘制触发区域
                Image("example2")
                    .resizable(resizingMode: .stretch)
                    .scaledToFit()
                    .clipped()
                    .position(x: 100, y: 100)
                    .frame(width: 100, height: 100)
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                guard currentBlackDrawing.points.count >= 1 else {
                                    currentBlackDrawing.points.append(value.location)
                                    return
                                }
                                // 仅保留最新的线段进行检测,避免遍历所有历史线段
                                let lastPoint = currentBlackDrawing.points.last!
                                currentBlackDrawing.points.append(value.location)
                                checkIntersection(lastSegment: (lastPoint, value.location))
                            }
                            .onEnded { _ in
                                blackDrawings.append(currentBlackDrawing)
                                currentBlackDrawing = Drawing()
                            }
                    )
            }
        }
    }
    
    // 通用的路径添加方法
    private func add(drawing: Drawing, toPath path: inout Path) {
        let points = drawing.points
        guard points.count > 1 else { return }
        for i in 0..<points.count-1 {
            path.move(to: points[i])
            path.addLine(to: points[i+1])
        }
    }
    
    // 仅检测新增线段与已绘制线条的交点
    private func checkIntersection(lastSegment: (CGPoint, CGPoint)) {
        guard !orangeDrawings.isEmpty else { return }
        let (current2, next2) = lastSegment
        
        for orangeDrawing in orangeDrawings {
            let points1 = orangeDrawing.points
            guard points1.count > 1 else { continue }
            
            for i in 0..<points1.count-1 {
                let current = points1[i]
                let next = points1[i+1]
                
                // 先做边界框预检测,快速排除不可能相交的线段
                guard doBoundingBoxesOverlap(a1: current, a2: next, b1: current2, b2: next2) else {
                    continue
                }
                
                // 精确相交计算
                let delta1x = next.x - current.x
                let delta1y = next.y - current.y
                let delta2x = next2.x - current2.x
                let delta2y = next2.y - current2.y
                
                let determinant = delta1x * delta2y - delta2x * delta1y
                guard abs(determinant) >= 0.0001 else {
                    // 平行或共线,跳过
                    continue
                }
                
                let ab = ((current.y - current2.y) * delta2x - (current.x - current2.x) * delta2y) / determinant
                guard ab > 0 && ab < 1 else { continue }
                
                let cd = ((current.y - current2.y) * delta1x - (current.x - current2.x) * delta1y) / determinant
                if cd > 0 && cd < 1 {
                    // 检测到相交,立即清空线条并终止所有计算
                    currentBlackDrawing.points.removeAll()
                    return
                }
            }
        }
    }
    
    // 边界框碰撞检测,快速过滤不相交线段
    private func doBoundingBoxesOverlap(a1: CGPoint, a2: CGPoint, b1: CGPoint, b2: CGPoint) -> Bool {
        let aMinX = min(a1.x, a2.x)
        let aMaxX = max(a1.x, a2.x)
        let aMinY = min(a1.y, a2.y)
        let aMaxY = max(a1.y, a2.y)
        
        let bMinX = min(b1.x, b2.x)
        let bMaxX = max(b1.x, b2.x)
        let bMinY = min(b1.y, b2.y)
        let bMaxY = max(b1.y, b2.y)
        
        return aMinX <= bMaxX && aMaxX >= bMinX && aMinY <= bMaxY && aMaxY >= bMinY
    }
}

优化效果说明

  • 计算量从原来的O(n*m)降至O(m)(n为当前绘制线段数,m为已绘制线段数),每次拖拽仅需计算最新添加的单段线段。
  • 边界框预检测能快速排除大部分不可能相交的线段,减少精确计算的次数。
  • 检测到相交后立即终止所有计算,避免不必要的资源消耗,保证实时绘制的流畅性。

内容的提问来源于stack exchange,提问作者264

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:15:57