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)
}
}