3

I am loading one PDF on PDF view using the PDF kit library. I added one custome view (same like PDF Annotation) on pdf view, and I am allowing users to move/drag that custom view on pdf view(within pdf view/container view) using UIPanGestureRecognizer. Here is a gif,

If you see this gif, there is one problem. That custom view is going outside of the pdf page. I want to restrict it. The custom view should move/drag within the pdf page only. How I can fix this? Is there a solution for it?

Here is the link sample project and all code - https://drive.google.com/file/d/1Ilhd8gp4AAxB_Q9G9swFbe4KQUHbpyGs/view?usp=sharing

Here is some code sample from project,

override func didMoveToSuperview() {
        addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(pan)))
    }

@objc func pan(_ gesture: UIPanGestureRecognizer) {
    translate(gesture.translation(in: self))
    gesture.setTranslation(.zero, in: self)
    setNeedsDisplay()
    print("Frames after moving : \(frame)")
}

and code used as an extension

extension  CGPoint {
    static func +(lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        .init(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
    }
    static func +=(lhs: inout CGPoint, rhs: CGPoint) {
        lhs.x += rhs.x
        lhs.y += rhs.y
    }
}
extension UIView {
    func translate(_ translation: CGPoint) {
        let destination = center + translation
        let minX = frame.width/2
        let minY = frame.height/2
        let maxX = superview!.frame.width-minX
        let maxY = superview!.frame.height-minY
        center = CGPoint(
            x: min(maxX, max(minX, destination.x)),
            y: min(maxY ,max(minY, destination.y)))
    }
}

Code - Get PDF Page Height and Width

 let page = pdfDocument.page(at: 0)
 let pageRect = page?.bounds(for: .mediaBox)

 print("pdf page width =", (pageRect?.size.width)!, "pdf page height =", (pageRect?.size.height)!)
Nikita Patil
  • 674
  • 1
  • 7
  • 17

1 Answers1

4

I would recommend PDFAnnotation rather that UIView for adding content onto the PDFView.

It is not so easy to compare a UIView's frame within a PDFView due to their coordinate systems being different.

Adding a PDFAnnotation to the PDFView works in sync with the PDF coordinate system whereas working with UIView, you will need to do some conversions between coordinate spaces and this can be tricky and not so accurate.

PDFKit PDFView coordinate system add PDF Annotation UIView

Here are some small changes I made to kind of get this to work with a view.

First in your SignatoryXibView I added this function to show a red border when we are close to the edge

func showWarning(_ show: Bool)
{
    if show
    {
        contentView1.layer.borderColor = UIColor.red.cgColor
        return
    }
    
    contentView1.layer.borderColor = UIColor.green.cgColor
}

I believe the SignatoryXibView should not be responsible for detecting and preventing going out of its superviews bounds so I created a protocol which the ViewController needs to conform to so that it can prevent the SignatoryXibView going out of the PDFView bounds.

protocol SignatoryViewDelegate: class
{
    func signatoryView(_ signatoryView: SignatoryXibView,
                       didReceivePanGesture gesture: UIPanGestureRecognizer)
}

class SignatoryXibView: UIView {

    // Add this
    weak var delegate: SignatoryViewDelegate?
    
    // .. rest of your code
    
    @objc
    func pan(_ gesture: UIPanGestureRecognizer)
    {
        // Hand off view translation handling to the delegate
        self.delegate?.signatoryView(self,
                                     didReceivePanGesture: gesture)
    }
}

Now in your UIView extension, you created a translate method which works well however, you do not want to translate the view's location. You want to first check if the translation will go out of the desired boundary and prevent the translation from happening.

extension UIView {
    
    // Your original code
    func translate(_ translation: CGPoint) {
        let destination = center + translation
        let minX = frame.width/2
        let minY = frame.height/2
        let maxX = superview!.frame.width-minX
        let maxY = superview!.frame.height-minY
        center = CGPoint(
            x: min(maxX, max(minX, destination.x)),
            y: min(maxY ,max(minY, destination.y)))
    }
    
    // I have added this function to return the new rect
    // that would happen if this view translated
    func translatedRect(_ translation: CGPoint) -> CGRect
    {
        // All of this is your calculation
        let destination = center + translation
        
        let minX = frame.width/2
        let minY = frame.height/2
        let maxX = superview!.frame.width-minX
        let maxY = superview!.frame.height-minY
        
        var rect = CGRect(origin: .zero,
                          size: CGSize(width: bounds.width,
                                       height: bounds.height))
        
        let midX = min(maxX, max(minX, destination.x))
        let midY = min(maxY ,max(minY, destination.y))
        
        // I am not translating here, just creating a new rect
        // of the view if it would be translated
        rect.origin = CGPoint(x: midX - frame.width/2,
                              y: midY - frame.height/2)
        
        return rect
    }
}

In your ViewController's loadPDF() function, make your view controller the delegate of the SignatoryXibView

// Add Sign on PdfView
// Your code unchanged
let customView = SignatoryXibView(frame: CGRect(x: 150, 
                                                y: 140, 
                                                width: 112, 
                                                height: 58))
customView.signatoryLabel.text = "John Doe"
self.pdfView.addSubview(customView)

//Add this
customView.delegate = self

Then finally you implement the delegate function we added earlier in the protocol to prevent the SignatoryXibView from going out the page's bounds.

extension ViewController: SignatoryViewDelegate
{
    func signatoryView(_ signatoryView: SignatoryXibView,
                       didReceivePanGesture gesture: UIPanGestureRecognizer)
    {
        // Get the location where the user has tapped
        let gestureTouchLocation = gesture.translation(in: signatoryView)
        
        // Get the new frame if the signature view would
        // be translated
        let updatedFrame
            = signatoryView.translatedRect(gestureTouchLocation)
        
        // Convert this rect from the Signature View's coordinate space
        // To the PDFView's coordinate space
        let updatedFrameConverted
        = pdfView.convert(updatedFrame,
                          to: pdfView.currentPage!)
        
        print("Updated frame: \(updatedFrame)")
        print("Updated frame converted: \(updatedFrameConverted)")
        print("Signature frame: \(signatoryView.frame)")
        print()
        
        // Retrieve the bounds of the current page
        // Handle the optional properly in your production app
        let pageRect = pdfView.currentPage!.bounds(for: .mediaBox)
        
        // Check if the new frame of SignatoryXibView is within the bounds of the pdf page
        if updatedFrameConverted.origin.x > CGFloat.zero &&
            updatedFrameConverted.origin.y + updatedFrameConverted.height < pageRect.height &&
            updatedFrameConverted.origin.x + updatedFrameConverted.width < pageRect.width &&
            updatedFrameConverted.origin.y > CGFloat.zero
        
        {
            // Since the view is within the bounds, you can update the views frame
            
            signatoryView.translate(gesture.translation(in: signatoryView))
            
            gesture.setTranslation(.zero, in: signatoryView)
            signatoryView.setNeedsDisplay()
            
            
            
            // Do not show any warning
            signatoryView.showWarning(false)
            
            return
        }
        
        // The view has reached the edge of the page so do not perform any view
        // translation and show the warning
        signatoryView.showWarning(true)
    }
}

The end result should give you the below result which prevents the view from going outside the page bounds and makes the view red to show it cannot go further:

PDFKit PDFView prevent Annotation boundary UIView margins

Final thoughts

  1. I recommend using PDFAnnotation when adding things to the PDF
  2. This will scale and scroll with the pages properly especially in scenarios like zooming a PDF where you might need to update your view again for it to be in the right location

Update

I updated some math in the func signatoryView function in the extension ViewController: SignatoryViewDelegate

This should give you better results in terms of figuring out the right boundary and also it will work when you zoom:

PDFView UIView boundary annotation page width page height

However, since it is not added as an annotation, it will not scroll with the page but the same boundary will be observed on the next page.

Shawn Frank
  • 4,381
  • 2
  • 19
  • 29
  • I will check the solution, and soon I will come back here and update you Shawn Frank. – Nikita Patil Feb 09 '22 at 04:49
  • Shawn Frank sir, your solution is working fine. There is a small issue. Can you check this gif file. https://user-images.githubusercontent.com/27955299/153130487-baa50ddb-6481-485c-bd57-6452759292fe.png I am checking this in iPhone 13 simulator. At the bottom, the placeholder is going a little down apart from no other issue. – Nikita Patil Feb 09 '22 at 05:56
  • using PDFAnnotation when adding things to the PDF - This is my next task. I will share one project link. There also the same thing I want to achieve, give me some time I will share you project link. – Nikita Patil Feb 09 '22 at 05:57
  • @NikitaPatil - please see my update, use the updated `func signatoryView` - I think it will give you better results. And please, you don't have to call me sir :) – Shawn Frank Feb 09 '22 at 07:02
  • Working perfectly and I accepted the answer. – Nikita Patil Feb 09 '22 at 07:11
  • 1
    And regarding PDFAnnoation you told right to the use. If I use UIView, while scrolling/zooming the pdf UIView will scroll, and if we use PDFAnnotation scroll issue won't come. I created a sample project with PDFAnnotation. Here is the link - https://drive.google.com/file/d/1KPK5Rh6_w0x9fQL35TwKAkyOeMPMHrwN/view?usp=drivesdk Here is also I need to fix the same issue. Once you are free, can u check this also. PDFAnnotation is going out of the pdf page boundary. If you want I will create a new question – Nikita Patil Feb 09 '22 at 07:11
  • @NikitaPatil - I think yes, better create another another question in the same way with some screenshot gif and your source code link. If not me, someone else might help. Once you create the question, you can add the link to the comments here and I will check it when I am free. – Shawn Frank Feb 09 '22 at 07:28
  • sure, Shawn. Thanks for your valuable time and efforts. It will help me a lot – Nikita Patil Feb 09 '22 at 07:34
  • i also tried this solution, it showing red color when going outside page, but not restricting annotation to go outside – Jagveer Singh Aug 11 '22 at 11:59