Is there a way to add a Gesture inside .onAppear() in SwiftUI?

75 Views Asked by At

I have a tricky problem and I hope someone can help me out here: I have a container that shows a PDF and I add an Annotation to that PDF. So far so good. Now I need that Annotation to be draggable.

First I wanted to handle that with UIPanGestureRecognizer. That doesn't work, because it needs an @objc function and I cannot put @objc in front of a function inside of a struct, which I need for the view ('cause SwiftUI)..

let dragAnnotation = UIPanGestureRecognizer(target: self, action: #selector(draggedAnnotation(sender:)))
...
// doesn't work with struct
@objc func draggedAnnotation(sender: UIPanGestureRecognizer) {

Second approach was a DragGesture, which would be perfect for SwiftUI, but I don't know how to add it inside the .onAppear method.

var body: some View {
            DocViewPDF(doc: self.pdfContainer.document)
        .onAppear {
        ...
        //doesn't work => gesture not gestureRecognizer
        annotation.addGestureRecognizer(dragAnnotation) 
        }
}

I need to do it in .onAppear, because the parameters are given there. Is there a way to add the DragGesture without .gesture maybe? Or any other way to handle this problem?

Edit: I made it work (more or less) like this: (new problem: the dragging feels weird; the finger/mouse moves faster than the dragged object)

import SwiftUI
import PDFKit

struct WelcomeView: View {
    @State private var pdfContainer: PDFView
    private var annotationImage: UIImage? // should be @State but can't be

    @State private var selectedAnnotation: PDFAnnotation?
@State var initalBounds: CGRect? = nil

    init(annotationImage: UIImage? = UIImage(named: "testSig.png")) {
        self.pdfContainer = PDFView()
        if let url = Bundle.main.url(forResource: "Test05", withExtension: "pdf") {
            pdfContainer.document = PDFDocument(url: url)
        }
        pdfContainer.autoScales = true
        self. annotationImage = UIImage(named: "testSig.png")
    }

    var body: some View {
        VStack {
            DocViewPDF(doc: self.pdfContainer.document).gesture(DragGesture().onChanged { g in
            guard let page = pdfContainer.currentPage else { return }
            guard let annotation = imageAnnotation else { return }
            if initalBounds == nil {
                initalBounds = annotation.bounds
            }
            let newX = initalBounds!.minX + g.translation.width
            let newY = initalBounds!.minY - g.translation.height
            annotation.bounds = CGRect(x: newX, y: newY, width: 200, height: 100)
        }.onEnded { _ in
            initalBounds = nil
        }).frame(maxWidth: .infinity, maxHeight: .infinity)
            Button("Add Annotation") {}
        }
        .onAppear {
            guard let annImage = annotationImage
            else { fatalError(Exception.kParameterMissing.localizedDescription) }
            guard let page = pdfContainer.currentPage
            else { fatalError(Exception.kFileIsEmpty.localizedDescription) }

            // place signature-draggable in center of currentPage
            let pageBoundaries: CGRect = page.bounds(for: .cropBox)
            let imageBoundaries: CGRect = .init(x: pageBoundaries.midX, y: pageBoundaries.midY, width: 200, height: 100)
            imageAnnotation = .init(image: sigImage, bounds: imageBoundaries, properties: nil)
            page.addAnnotation(imageAnnotation)
        }
    }

class ImageAnnotation: PDFAnnotation {
    private let image: UIImage

    // A custom init that sets the type to Stamp on default and assigns our image variable
    init(image: UIImage, bounds: CGRect, properties: [AnyHashable: Any]?) {
        self.image = image
        super.init(bounds: bounds, forType: PDFAnnotationSubtype.stamp, withProperties: properties)
    }

    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        fatalError("required by PDFAnnotation - not needed for Image")
    }

    override func draw(with box: PDFDisplayBox, in context: CGContext) {
        // Get the CGImage of our image
        guard let cgImage = image.cgImage else { return }
        // Draw our CGImage in the context of our PDFAnnotation bounds
        context.draw(cgImage, in: bounds)
    }
}
0

There are 0 best solutions below