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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 06:45:06