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

