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
相关产品推荐
相关产品推荐

