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

SwiftUI中执行拖动手势时如何持续更新视图实现绘画功能

问题根源

你收到的警告是因为DragGesture.updating的闭包属于视图更新周期的一部分,只能用来更新绑定的@GestureState属性,不能在这个闭包里执行修改其他@State属性的副作用操作,你在这里调用paint()直接修改image状态,会打断正在进行的视图渲染流程,所以触发了异常警告。

修复方案

做两处核心调整即可解决问题,同时满足连续绘画的需求:

  • 调整状态存储:不要把SwiftUI视图类型Image作为状态存储,改为存储底层的UIImage,避免不必要的视图实例重建。
  • 拆分手势逻辑:updating仅负责更新手势位置,用onChange监听位置变化触发绘画操作,脱离视图更新周期,修改状态不会触发异常。

完整修改后代码

import SwiftUI
import CoreImage

struct ContentView: View {
    // 改为存储UIImage,不存SwiftUI的Image视图
    @State private var canvas: UIImage?
    @GestureState private var dragLocation = CGPoint.zero
    // 提前初始化CI上下文,避免重复创建损耗性能
    private let ciContext = CIContext()

    var body: some View {
        VStack {
            if let canvas = canvas {
                Image(uiImage: canvas)
                    .resizable()
                    .scaledToFit()
            }
        }
        .onAppear(perform: newPainting)
        // 监听拖拽位置变化,触发绘画,这个回调不在视图更新周期内,修改状态不会报错
        .onChange(of: dragLocation) { newLocation in
            // 坐标转换:把视图显示坐标转成画布128x128的像素坐标,避免绘画位置偏移
            let canvasSize = CGSize(width: 128, height: 128)
            // 实际使用时替换为图像视图的真实显示大小
            let viewSize = UIScreen.main.bounds.size
            let scaleX = canvasSize.width / viewSize.width
            let scaleY = canvasSize.height / viewSize.height
            let convertedLocation = CGPoint(x: newLocation.x * scaleX, y: newLocation.y * scaleY)
            
            paint(location: convertedLocation)
        }
        .gesture(
            DragGesture(minimumDistance: 0) // minimumDistance设为0,点击也能触发绘画
                .updating($dragLocation) { value, state, _ in
                    // 这里只更新GestureState,不做其他操作
                    state = value.location
                }
        )
    }

    func newPainting() {
        guard let newPainting = createBlankCanvas(size: CGSize(width: 128, height: 128)) else {
            print("failed to create a blank canvas")
            return
        }
        canvas = newPainting
    }

    func createBlankCanvas(size: CGSize, filledWithColor color: UIColor = .clear, scale: CGFloat = 0.0, opaque: Bool = false) -> UIImage? {
        let rect = CGRect(origin: .zero, size: size)
        UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
        color.set()
        UIRectFill(rect)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }

    func paint(location: CGPoint) {
        guard let currentCanvas = canvas, let ciImage = CIImage(image: currentCanvas) else { return }
        // 这里替换为你自己的CoreImage操作逻辑,以下为示例代码
        let filter = CIFilter(name: "CIRectangleGenerator")!
        filter.setValue(CIColor.red, forKey: kCIInputColorKey)
        filter.setValue(CIVector(cgRect: CGRect(x: location.x - 5, y: location.y - 5, width: 10, height: 10)), forKey: kCIInputExtentKey)
        guard let shape = filter.outputImage else { return }
        let compositeFilter = CIFilter(name: "CISourceOverCompositing")!
        compositeFilter.setValue(shape, forKey: kCIInputImageKey)
        compositeFilter.setValue(ciImage, forKey: kCIInputBackgroundImageKey)
        guard let outputImage = compositeFilter.outputImage else { return }

        if let cgimg = ciContext.createCGImage(outputImage, from: outputImage.extent) {
            let uiImage = UIImage(cgImage: cgimg)
            canvas = uiImage
        }
    }
}

补充优化建议

如果需要提升连续绘画的流畅度,可以把CoreImage的计算逻辑放到后台队列执行,完成后再切回主队列更新canvas状态,避免阻塞主线程渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 04:06:02