SwiftUI绘制带可交互圆点的折线时append报错及ForEach使用问题
问题修复与功能实现
一、基础报错修复
你遇到的No exact matches in call to instance method 'append'错误是类型不匹配导致的:
- 你声明的
PointArray是[CGPoint]类型,但你往数组中插入的是自定义Positions结构体实例,类型不对应自然调用append失败。
除此之外你的原有实现还有两个不符合SwiftUI规范的问题:
- 不要用全局变量存储视图状态,SwiftUI是状态驱动的,应该用
@State等属性包装器管理数据 - 不要在
Shape的path(in:)方法中修改外部变量,该方法是纯函数,仅用来生成绘制路径,不应该包含副作用 - 你代码中使用的
DrawingZoneWidth、DrawingZoneHeight未定义,会触发编译错误
二、完整可运行实现代码
import SwiftUI // 控制点模型,支持唯一识别、存储位置和选中状态 struct ControlPoint: Identifiable { let id = UUID() var position: CGPoint var isSelected: Bool = false } // 可复用自定义折线/贝塞尔曲线Shape struct CustomCurve: Shape { var points: [CGPoint] // 开启后自动渲染为平滑二次贝塞尔曲线,关闭则为折线 var isSmoothBezier: Bool = false func path(in rect: CGRect) -> Path { var path = Path() guard !points.isEmpty else { return path } path.move(to: points[0]) if isSmoothBezier { // 平滑贝塞尔曲线实现 for i in 1..<points.count { let prevPoint = points[i-1] let currentPoint = points[i] let midPoint = CGPoint(x: (prevPoint.x + currentPoint.x)/2, y: (prevPoint.y + currentPoint.y)/2) path.addQuadCurve(to: midPoint, control: prevPoint) } path.addLine(to: points.last!) } else { // 折线实现 for point in points.dropFirst() { path.addLine(to: point) } } return path } } struct TouchCurveView: View { // 状态管理所有控制点 @State private var controlPoints: [ControlPoint] = [] // 自定义画布尺寸,可根据需求修改 private let canvasSize: CGSize = CGSize(width: 350, height: 300) // 默认生成的控制点数量,可根据需求修改 private let defaultPointCount: Int = 10 var body: some View { ZStack(alignment: .topLeading) { // 绘制折线/贝塞尔曲线 CustomCurve(points: controlPoints.map{ $0.position }, isSmoothBezier: false) .stroke(Color.red, style: StrokeStyle(lineWidth: 4, lineCap: .round, lineJoin: .round)) // 遍历渲染所有控制点,解决你之前ForEach无法渲染的问题 ForEach($controlPoints) { $point in Circle() .fill(point.isSelected ? Color.blue : Color.white) .stroke(Color.black, lineWidth: 2) .frame(width: 24, height: 24) .position(point.position) // 点击手势切换选中状态,修改圆点颜色 .onTapGesture { point.isSelected.toggle() } // 拖拽手势修改圆点位置,曲线自动跟随更新 .gesture( DragGesture() .onChanged { value in point.position = value.location } ) } } .frame(width: canvasSize.width, height: canvasSize.height) .border(Color.gray.opacity(0.3), width: 1) .onAppear { // 初始化默认水平排列的控制点 let xStep = canvasSize.width / CGFloat(defaultPointCount - 1) let defaultY = canvasSize.height / 2 for i in 0..<defaultPointCount { let x = xStep * CGFloat(i) controlPoints.append(ControlPoint(position: CGPoint(x: x, y: defaultY))) } } } } struct TouchCurveView_Previews: PreviewProvider { static var previews: some View { TouchCurveView() } }
三、功能说明
- 初始化自动生成一排水平排列的控制点,连接为红色折线
- 点击圆点切换填充色:未选中为白色,选中为蓝色
- 拖拽任意圆点可自由移动位置,折线会实时跟随更新
- 需要切换为平滑贝塞尔曲线时,仅需将
CustomCurve的isSmoothBezier参数设置为true即可 - 所有组件支持复用,可自定义调整画布尺寸、控制点数量、圆点大小、线条样式等参数
内容的提问来源于stack exchange,提问作者CharlieWhite
相关产品推荐
相关产品推荐

