使用SwiftUI实现PencilKit撤消功能

使用SwiftUI实现PencilKit撤消功能,swiftui,uiviewrepresentable,pencilkit,Swiftui,Uiviewrepresentable,Pencilkit,编辑:由于一些反馈,我已经能够得到这个部分工作的更新代码,以反映当前的更改 尽管该应用程序似乎按预期工作,但我仍然收到“修改状态…”警告。如何在UpdateUI视图中更新视图的图形,并使用canvasViewDrawingDidChange将新图形推送到堆栈上,而不会导致此问题?我曾尝试将其包装在分派调用中,但这只会创建一个无限循环 我试图在UIViewRepresentable PKCanvasView中实现撤销功能。我有一个名为WriterView的父SwiftUI视图,其中包含两个按钮和画

编辑:由于一些反馈,我已经能够得到这个部分工作的更新代码,以反映当前的更改

尽管该应用程序似乎按预期工作,但我仍然收到“修改状态…”警告。如何在UpdateUI视图中更新视图的图形,并使用canvasViewDrawingDidChange将新图形推送到堆栈上,而不会导致此问题?我曾尝试将其包装在分派调用中,但这只会创建一个无限循环

我试图在UIViewRepresentable PKCanvasView中实现撤销功能。我有一个名为WriterView的父SwiftUI视图,其中包含两个按钮和画布

以下是父视图:

结构WriterView:视图{ @状态变量绘图:[PKDrawing]=[PKDrawing] var body:一些观点{ 间距:10{ 钮扣{ self.drawings=[] } 钮扣{ 如果!self.drawings.isEmpty{ self.drawings.removeLast } } MyCanvasdrawings:$drawings } } } 以下是我如何实现UIViewRepresentable的:

结构MyCanvas:UIViewRepresentable{ @绑定var图纸:[PKDrawing] func makeUIViewcontext:Context->PKCanvasView{ 让canvas=PKCanvasView canvas.delegate=context.coordinator 返回画布 } func updateUIView_uiView:PKCanvasView,context:context{ uiView.drawing=self.drawings.last??PKDrawing } func makeCoordinator->Coordinator{ 协调人自身图纸 } 类协调器:NSObject,PKCanvasViewDelegate{ @装订图纸:[PKDrawing] 初始图纸:装订{ 自身图纸=图纸 } func canvasViewDrawingDidChange\uCanvasView:PKCanvasView{ drawings.appendcanvasView.drawing } } } 我得到以下错误:

[SwiftUI]在视图更新期间修改状态,这将导致未定义的行为

这大概是由我的协调器的did change函数引起的,但我不确定如何解决这个问题。最好的方法是什么


谢谢

我认为错误可能来自私有func clearCanvas 和私人func。请尝试以下操作,看看是否有效:

private func clearCanvas() {
 DispatchQueue.main.async {
    self.drawings = [PKDrawing()]
 }
}
类似地,用于绘制


如果是来自canvasViewDrawingDidChange,请使用相同的技巧。

我认为错误可能来自私有func clearCanvas 和私人func。请尝试以下操作,看看是否有效:

private func clearCanvas() {
 DispatchQueue.main.async {
    self.drawings = [PKDrawing()]
 }
}
类似地,用于绘制


如果它来自canvasViewDrawingDidChange,请使用相同的技巧。

我有一些方法可以解决这个问题:

struct MyCanvas: UIViewRepresentable {
@Binding var drawings: [PKDrawing]

func makeUIView(context: Context) -> PKCanvasView {
    let canvas = PKCanvasView()
    canvas.delegate = context.coordinator
    return canvas
 }

func updateUIView(_ canvas: PKCanvasView, context: Context) { }

func makeCoordinator() -> Coordinator {
    Coordinator(self._drawings)
}

class Coordinator: NSObject, PKCanvasViewDelegate {
    @Binding var drawings: [PKDrawing]

    init(_ drawings: Binding<[PKDrawing]>) {
        self._drawings = drawings
    }

    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
        self.drawings.append(canvasView.drawing)
    }
}
}

我有一些关于这方面的工作:

struct MyCanvas: UIViewRepresentable {
@Binding var drawings: [PKDrawing]

func makeUIView(context: Context) -> PKCanvasView {
    let canvas = PKCanvasView()
    canvas.delegate = context.coordinator
    return canvas
 }

func updateUIView(_ canvas: PKCanvasView, context: Context) { }

func makeCoordinator() -> Coordinator {
    Coordinator(self._drawings)
}

class Coordinator: NSObject, PKCanvasViewDelegate {
    @Binding var drawings: [PKDrawing]

    init(_ drawings: Binding<[PKDrawing]>) {
        self._drawings = drawings
    }

    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
        self.drawings.append(canvasView.drawing)
    }
}
}

我想我用了一种不同的方法在没有警告的情况下工作

struct ContentView: View {

let pkCntrl = PKCanvasController()

var body: some View {
    VStack(spacing: 10) {
        Button("Clear") {
            self.pkCntrl.clear()
        }
        Spacer()
        Button("Undo") {
            self.pkCntrl.undoDrawing()
        }
        Spacer()
        MyCanvas(cntrl: pkCntrl)
    }
}

}

struct MyCanvas: UIViewRepresentable {
var cntrl: PKCanvasController

func makeUIView(context: Context) -> PKCanvasView {
    cntrl.canvas = PKCanvasView()
    cntrl.canvas.delegate = context.coordinator
    cntrl.canvas.becomeFirstResponder()
    return cntrl.canvas
}

func updateUIView(_ uiView: PKCanvasView, context: Context) { }

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

class Coordinator: NSObject, PKCanvasViewDelegate {
    var parent: MyCanvas

    init(_ uiView: MyCanvas) {
        self.parent = uiView
    }

    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
        if !self.parent.cntrl.didRemove {
            self.parent.cntrl.drawings.append(canvasView.drawing)
        }
    }
}
}

class PKCanvasController {
var canvas = PKCanvasView()
var drawings = [PKDrawing]()
var didRemove = false

func clear() {
    canvas.drawing = PKDrawing()
    drawings = [PKDrawing]()
}

func undoDrawing() {
    if !drawings.isEmpty {
        didRemove = true
        drawings.removeLast()
        canvas.drawing = drawings.last ?? PKDrawing()
        didRemove = false
    }
}
}

我想我用了一种不同的方法在没有警告的情况下工作

struct ContentView: View {

let pkCntrl = PKCanvasController()

var body: some View {
    VStack(spacing: 10) {
        Button("Clear") {
            self.pkCntrl.clear()
        }
        Spacer()
        Button("Undo") {
            self.pkCntrl.undoDrawing()
        }
        Spacer()
        MyCanvas(cntrl: pkCntrl)
    }
}

}

struct MyCanvas: UIViewRepresentable {
var cntrl: PKCanvasController

func makeUIView(context: Context) -> PKCanvasView {
    cntrl.canvas = PKCanvasView()
    cntrl.canvas.delegate = context.coordinator
    cntrl.canvas.becomeFirstResponder()
    return cntrl.canvas
}

func updateUIView(_ uiView: PKCanvasView, context: Context) { }

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}

class Coordinator: NSObject, PKCanvasViewDelegate {
    var parent: MyCanvas

    init(_ uiView: MyCanvas) {
        self.parent = uiView
    }

    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
        if !self.parent.cntrl.didRemove {
            self.parent.cntrl.drawings.append(canvasView.drawing)
        }
    }
}
}

class PKCanvasController {
var canvas = PKCanvasView()
var drawings = [PKDrawing]()
var didRemove = false

func clear() {
    canvas.drawing = PKDrawing()
    drawings = [PKDrawing]()
}

func undoDrawing() {
    if !drawings.isEmpty {
        didRemove = true
        drawings.removeLast()
        canvas.drawing = drawings.last ?? PKDrawing()
        didRemove = false
    }
}
}

我终于意外地发现了如何使用UndoManager来实现这一点。我仍然不确定这到底是为什么,因为我从来都不需要叫self.undoManager?.registerUndo。如果您理解为什么我从不需要注册活动,请发表评论

以下是我的工作父视图:

结构编写器:视图{ @环境\.undoManager私有变量undoManager @国家私有var canvasView=PKCanvasView var body:一些观点{ 间距:10{ 钮扣{ canvasView.drawing=PKDrawing } 钮扣{ 撤消管理器?撤消 } 钮扣{ 撤消管理器?重做 } MyCanvasView:$canvasView } } } 以下是我的工作子视图:

结构MyCanvas:UIViewRepresentable{ @绑定var canvasView:PKCanvasView func makeUIViewcontext:Context->PKCanvasView{ canvasView.drawingPolicy=.anyInput canvasView.tool=PKInkingTool.pen,颜色:。黑色,宽度:15 返回画布视图 } func updateUIView_uvasview:PKCanvasView,context:context{} }
这无疑更像是SwiftUI的预期方法,而且肯定比我之前的尝试更优雅。

我终于意外地发现了如何使用UndoManager实现这一点。我仍然不确定这到底是为什么,因为我从来都不需要叫self.undoManager?.registerUndo。如果您理解为什么我从不需要注册活动,请发表评论

以下是我的工作父视图:

结构编写器:视图{ @环境\.undoManager私有变量undoManager @国家私有var canvasView=PKCanvasView var body:一些观点{ 间距:10{ 钮扣{ canvasView.drawing=PKDrawing } 钮扣{ 撤消管理器?撤消 } 钮扣{ 撤消管理器?重做 } MyCanvasView:$canvasView } } } 以下是我的工作子视图:

结构MyCanvas:UIViewRepresentable{ @绑定var canvasView:PKCanvasView func makeUIViewcontext:Context->PKCanvasView{ canvasView.drawingPolicy=.anyInput canvasView.tool=PKInkingTool.pen,颜色:。黑色,宽度:15 返回画布视图 } func updateUIView_uvasview:PKCanvasView,context:context{} }
这当然更像是SwiftUI的预期方法,而且肯定比我之前的尝试更优雅。

为了完整起见,如果您想展示PKToolPicker,这里是我的UIViewRepresentable

import Foundation
import SwiftUI
import PencilKit

struct PKCanvasSwiftUIView : UIViewRepresentable {

let canvasView = PKCanvasView()

#if !targetEnvironment(macCatalyst)
let coordinator = Coordinator()

class Coordinator: NSObject, PKToolPickerObserver {
    // initial values 
    var color = UIColor.black
    var thickness = CGFloat(30)

    func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) {
        if toolPicker.selectedTool is PKInkingTool {
            let tool = toolPicker.selectedTool as! PKInkingTool
            self.color = tool.color
            self.thickness = tool.width
        }
    }
    func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
        if toolPicker.selectedTool is PKInkingTool {
            let tool = toolPicker.selectedTool as! PKInkingTool
            self.color = tool.color
            self.thickness = tool.width
        }
    }
}
func makeCoordinator() -> PKCanvasSwiftUIView.Coordinator {
    return Coordinator()
}
#endif

func makeUIView(context: Context) -> PKCanvasView {
    canvasView.isOpaque = false
    canvasView.backgroundColor = UIColor.clear
    canvasView.becomeFirstResponder()
    #if !targetEnvironment(macCatalyst)
    if let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first,
        let toolPicker = PKToolPicker.shared(for: window) {
        toolPicker.addObserver(canvasView)
        toolPicker.addObserver(coordinator)
        toolPicker.setVisible(true, forFirstResponder: canvasView)
    }
    #endif
    return canvasView
}

func updateUIView(_ uiView: PKCanvasView, context: Context) {

}
} 

为了完整起见,如果您想展示PKToolPicker,下面是我的UIViewRepresentable

import Foundation
import SwiftUI
import PencilKit

struct PKCanvasSwiftUIView : UIViewRepresentable {

let canvasView = PKCanvasView()

#if !targetEnvironment(macCatalyst)
let coordinator = Coordinator()

class Coordinator: NSObject, PKToolPickerObserver {
    // initial values 
    var color = UIColor.black
    var thickness = CGFloat(30)

    func toolPickerSelectedToolDidChange(_ toolPicker: PKToolPicker) {
        if toolPicker.selectedTool is PKInkingTool {
            let tool = toolPicker.selectedTool as! PKInkingTool
            self.color = tool.color
            self.thickness = tool.width
        }
    }
    func toolPickerVisibilityDidChange(_ toolPicker: PKToolPicker) {
        if toolPicker.selectedTool is PKInkingTool {
            let tool = toolPicker.selectedTool as! PKInkingTool
            self.color = tool.color
            self.thickness = tool.width
        }
    }
}
func makeCoordinator() -> PKCanvasSwiftUIView.Coordinator {
    return Coordinator()
}
#endif

func makeUIView(context: Context) -> PKCanvasView {
    canvasView.isOpaque = false
    canvasView.backgroundColor = UIColor.clear
    canvasView.becomeFirstResponder()
    #if !targetEnvironment(macCatalyst)
    if let window = UIApplication.shared.windows.filter({$0.isKeyWindow}).first,
        let toolPicker = PKToolPicker.shared(for: window) {
        toolPicker.addObserver(canvasView)
        toolPicker.addObserver(coordinator)
        toolPicker.setVisible(true, forFirstResponder: canvasView)
    }
    #endif
    return canvasView
}

func updateUIView(_ uiView: PKCanvasView, context: Context) {

}
} 


不幸的是,这没有帮助。我试图通过删除清除和撤消功能来隔离问题。这似乎是updateUIView和canvasViewDrawingDidChange的一些问题。我想在某个时候我需要将新图形推到堆栈上,但不是在主体再次渲染时?我注意到您有[PKDrawing],如果这是[PKDrawing],不幸的是,这没有帮助。我试图通过删除清除和撤消功能来隔离问题。这似乎是updateUIView和canvasViewDrawingDidChange的一些问题。我想在某个时候我需要将新的绘图推到堆栈上,但不是在主体再次渲染时?我注意到您有[PKDrawing],这应该是[PKDrawing]吗?这几乎可以工作了。视图没有反映图形数组,因此我添加了uiView.drawing=self.drawings.last??PKDrawing到updateUIView函数。即使它现在看起来起作用了,它仍然给了我同样的警告。你有什么建议吗?我试过很多方法,比如使用@ObservedObject,但都不管用。我无法摆脱这个警告。这几乎奏效了。视图没有反映图形数组,因此我添加了uiView.drawing=self.drawings.last??PKDrawing到updateUIView函数。即使它现在看起来起作用了,它仍然给了我同样的警告。你有什么建议吗?我试过很多方法,比如使用@ObservedObject,但都不管用。我无法摆脱这个警告。非常感谢你!这很有效,看起来相当干净/简单。这有点麻烦,但我认为这是一个很好的起点。当你撤销时,会出现一些奇怪的问题,再写一些,然后再次按撤销。这些奇怪的问题是因为我们没有撤销单个笔划,更像是撤销一个屏幕截图。绘画不是笔画,而是当时所有的笔画。非常感谢!这很有效,看起来相当干净/简单。这有点麻烦,但我认为这是一个很好的起点。当你撤销时,会出现一些奇怪的问题,再写一些,然后再次按撤销。这些奇怪的问题是因为我们没有撤销单个笔划,更像是撤销一个屏幕截图。绘图不是笔划,而是当时的所有笔划。这看起来是一个更好的解决方案。我在上看到了对环境的一个很好的解释:它基本上说这是一个预定义的系统范围的环境变量,它被传递到每个视图中。都为你做了。在我自己的代码中,iPad上的绘图工具内置了撤销/重做按钮,但iPhone上没有。在Mac catalist上根本没有工具,你必须自己做,所以我认为这就是你编写代码的目的。唯一可以改进的方法是,如果清除按钮可以撤消。。。但我现在不知道该怎么做:可悲的是:这看起来是一个更好的解决方案。我在上看到了对环境的一个很好的解释:它基本上说这是一个预定义的系统范围的环境变量,它被传递到每个视图中。都为你做了。在我自己的代码中,iPad上的绘图工具内置了撤销/重做按钮,但iPhone上没有。在Mac catalist上根本没有工具,你必须自己做,所以我认为这就是你编写代码的目的。唯一可以改进的方法是,如果清除按钮可以撤消。。。但我现在不知道该怎么做:悲伤: