限制 move/drag PDF 页面边界内的 pdf 注释

Restrict to move/drag the pdf annotation within PDF Page boundary

我正在使用 PDF 套件库在 PDF 视图中加载一个 PDF。我在 pdf 视图上添加了一个自定义视图(与 PDF 注释相同),我允许用户使用 UIPanGestureRecognizer move/drag 在 pdf 视图(在 pdf view/container 视图中)自定义视图。 这是一张动图,

如果您看到这张 gif,说明有一个问题。该自定义视图超出了 pdf 页面。我想限制它。自定义视图应仅在 pdf 页面内 move/drag。 我该如何解决这个问题?有解决办法吗?

这是 link 示例项目和所有代码 - https://drive.google.com/file/d/1Ilhd8gp4AAxB_Q9G9swFbe4KQUHbpyGs/view?usp=sharing

这是项目中的一些代码示例,

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

和用作扩展的代码

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

代码 - 获取 PDF 页面高度和宽度

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

我建议 PDFAnnotation 而不是 UIView 以将内容添加到 PDFView

比较UIView's帧和PDFView帧并不容易,因为它们的坐标系不同。

PDFAnnotation 添加到 PDFView 与 PDF 坐标系同步工作,而使用 UIView 时,您将需要在坐标空间之间进行一些转换,这可能很棘手,但并非如此准确。

我做了一些小改动,以使其与视图一起工作。

首先在你的SignatoryXibView我添加了这个功能,当我们接近边缘时显示红色边框

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

我认为 SignatoryXibView 不应该负责检测和防止超出其超级视图范围,所以我创建了一个 ViewController 需要遵守的协议,以便它可以防止 SignatoryXibView 超出 PDFView 范围。

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

现在,在您的 UIView 扩展中,您创建了一个运行良好的翻译方法,但是您不想翻译视图的位置。您想首先检查翻译是否会超出所需边界并防止翻译发生。

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

在你的 ViewController's loadPDF() 函数中,让你的视图控制器成为 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

然后最后实现我们之前在协议中添加的委托函数,以防止 SignatoryXibView 超出页面边界。

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

最终结果应为您提供以下结果,该结果可防止视图超出页面边界并使视图变红以表明它无法继续前进:

最后的想法

  1. 我建议在向 PDF 添加内容时使用 PDFAnnotation
  2. 这将正确缩放和滚动页面,尤其是在缩放 PDF 等场景中,您可能需要再次更新视图以使其位于正确的位置

更新

我在 extension ViewController: SignatoryViewDelegate

func signatoryView 函数中更新了一些数学运算

这应该会在确定正确边界方面为您提供更好的结果,并且在您缩放时它也会起作用:

但是,由于没有添加为注释,因此不会随页面滚动,但在下一页会观察到相同的边界。