使用SwiftUI实现PencilKit撤消功能
编辑:由于一些反馈,我已经能够得到这个部分工作的更新代码,以反映当前的更改 尽管该应用程序似乎按预期工作,但我仍然收到“修改状态…”警告。如何在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函数引起的,但我不确定如何解决这个问题。最好的方法是什么使用SwiftUI实现PencilKit撤消功能,swiftui,uiviewrepresentable,pencilkit,Swiftui,Uiviewrepresentable,Pencilkit,编辑:由于一些反馈,我已经能够得到这个部分工作的更新代码,以反映当前的更改 尽管该应用程序似乎按预期工作,但我仍然收到“修改状态…”警告。如何在UpdateUI视图中更新视图的图形,并使用canvasViewDrawingDidChange将新图形推送到堆栈上,而不会导致此问题?我曾尝试将其包装在分派调用中,但这只会创建一个无限循环 我试图在UIViewRepresentable PKCanvasView中实现撤销功能。我有一个名为WriterView的父SwiftUI视图,其中包含两个按钮和画
谢谢 我认为错误可能来自私有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上根本没有工具,你必须自己做,所以我认为这就是你编写代码的目的。唯一可以改进的方法是,如果清除按钮可以撤消。。。但我现在不知道该怎么做:悲伤: