Реализация функции отмены PencilKit с помощью SwiftUI - PullRequest
0 голосов
/ 09 марта 2020

Редактировать: Благодаря некоторым отзывам я смог получить эту частично работоспособную (обновленный код, чтобы отразить текущие изменения).

Несмотря на то, что приложение работает как и предполагалось, я все еще получаю предупреждение «Изменение состояния ...». Как я могу обновить чертеж вида в updateUIView и pu sh новые чертежи в стек с помощью canvasViewDrawingDidChange, не вызывая этой проблемы? Я попытался обернуть его в диспетчерский вызов, но это просто создает бесконечное число l oop.


Я пытаюсь реализовать функцию отмены в UIViewRepresentable (PKCanvasView). У меня есть родительское представление SwiftUI под названием WriterView, которое содержит две кнопки и холст.

Вот родительское представление:

struct WriterView: View {
    @State var drawings: [PKDrawing] = [PKDrawing()]

    var body: some View {
        VStack(spacing: 10) {
            Button("Clear") {
                self.drawings = []
            }
            Button("Undo") {
                if !self.drawings.isEmpty {
                    self.drawings.removeLast()
                }
            }
            MyCanvas(drawings: $drawings)
        }
    }
}

Вот как я реализовал UIViewRepresentable:

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

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

    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        uiView.drawing = self.drawings.last ?? PKDrawing()
    }

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

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

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

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

Я получаю следующую ошибку:

[SwiftUI] Изменение состояния во время обновления представления, это приведет к неопределенному поведению.

Предположительно, это вызвано моим координатор изменил функцию, но я не уверен, как это исправить. Как лучше всего подойти к этому?

Спасибо!

Ответы [ 5 ]

1 голос
/ 12 марта 2020

Я наконец (случайно) разобрался, как это сделать с помощью UndoManager. Я до сих пор точно не знаю , почему это работает, потому что мне никогда не нужно звонить self.undoManager?.registerUndo(). Прокомментируйте, если вы понимаете, почему мне никогда не нужно регистрировать событие.

Вот мой рабочий вид для родителей:

struct Writer: View {
    @Environment(\.undoManager) var undoManager
    @State private var canvasView = PKCanvasView()

    var body: some View {
        VStack(spacing: 10) {
            Button("Clear") {
                self.canvasView.drawing = PKDrawing()
            }
            Button("Undo") {
                self.undoManager?.undo()
            }
            Button("Redo") {
                self.undoManager?.redo()
            }
            MyCanvas(canvasView: $canvasView)
        }
    }
}

Вот мой рабочий вид для детей:

struct MyCanvas: UIViewRepresentable {
    @Binding var canvasView: PKCanvasView

    func makeUIView(context: Context) -> PKCanvasView {
        self.canvasView.tool = PKInkingTool(.pen, color: .black, width: 15)
        return canvasView
    }

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

Это, безусловно, больше похоже на предполагаемый подход для SwiftUI и, безусловно, более элегантно, чем попытки, которые я делал ранее.

0 голосов
/ 12 марта 2020

только для полноты, и если вы хотите показать 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) {

}
} 
0 голосов
/ 09 марта 2020

У меня что-то работает с этим:

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)
    }
}
}
0 голосов
/ 10 марта 2020

Я думаю, что у меня что-то работает без предупреждения, используя другой подход.

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
    }
}
}
0 голосов
/ 09 марта 2020

Я думаю, что ошибка, вероятно, происходит из частного веселья c clearCanvas () и частного веселья c undoDrawing (). Попробуйте, чтобы увидеть, работает ли он:

private func clearCanvas() {
 DispatchQueue.main.async {
    self.drawings = [PKDrawing()]
 }
}

Аналогично для undoDrawing ().

Если это из canvasViewDrawingDidChange, проделайте тот же трюк.

...