Overlaying PKCanvasView on top of WKWebView
Asked Answered
T

1

7

I'm trying to integrate Apple Pencil with a WKWebView.

Desired behavior:

  • Using pencil allows you to draw on the webpage with all the fancy PencilKit integrations (PKToolPicker, etc)
  • Using fingers allows you to scroll through the webpage, and the scrolling is synced to the webview.

TAKE 2

So after spending a bit more time with this, I've arrived at an extremely hacky solution, and I don't actually like the solution:

  • Create a WKWebView and a transparent PKCanvasView on top of it.
  • Override the hitTest of the `PKCanvasView to always return nil.
  • Add the drawingGestureRecognizer of the PKCanvasView to the WKWebView
  • Make sure that drawingGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.pencil.rawValue)] is set for the PKCanvasView

This approach works but it removes a ton of flexibility for the implementation.

TAKE 1

So far I've tried two approaches:

  • Selectively cancel user input on the canvas when I detect it's coming from a finger. This didn't work because there was no way for me to detect this before the event was consumed by the view.

  • Create a transparant superview, and manually call touchesBegan/touchesMoved/touchesEnded for the PKCanvasView and WKWebView when I did my detection. Unfortunately, this didn't work either as calling those methods didn't do anything.

This is some basic code I have so far:

struct SUICanvasView: UIViewControllerRepresentable {
    let url: URL?
    func makeUIViewController(context: Context) -> ZRCanvasReader {
        let canvasReader = ZRCanvasReader()
        canvasReader.openUrl(url: url)
        return canvasReader
    }

    func updateUIViewController(_ uiViewController: ZRCanvasReader, context: Context) {

    }

    typealias UIViewControllerType = ZRCanvasReader
}

class ZRCanvasReader: UIViewController {
    lazy var canvas: PKCanvasView = {
        let v = PKCanvasView()
        v.isOpaque = false
        v.backgroundColor = .clear
        return v
    }()

    lazy var toolPicker: PKToolPicker = {
        let toolPicker = PKToolPicker()
        return toolPicker
    }()

    lazy var webView: WKWebView = {
        let prefs = WKWebpagePreferences()
        prefs.allowsContentJavaScript = true

        let config = WKWebViewConfiguration()
        config.defaultWebpagePreferences = prefs


        let webview = WKWebView(frame: .zero, configuration: config)
        return webview
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(webView)
        view.addSubview(canvas)

        webView.frame = view.frame
        canvas.frame = view.frame

        toolPicker.addObserver(canvas)
        toolPicker.setVisible(true, forFirstResponder: canvas)
    }

    func openUrl(url: URL?) {
        guard let loadingUrl = url else {
            return
        }
        let request = URLRequest(url: loadingUrl)
        webView.load(request)
    }
}
Turino answered 18/2, 2021 at 0:26 Comment(0)
D
0
// Set PKCanvasView to same size as UIScrollView's content size
canvasView = PKCanvasView(frame: CGRect(origin: .zero, size: scrollView.contentSize))
scrollView.addSubview(canvasView)
// Disable UIScrollView scroll with one finger
scrollView.panGestureRecognizer.minimumNumberOfTouches = 2
Diaphragm answered 15/4, 2023 at 10:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.