swiftpdfkit

How to track onReachBottom event in PDFKit


I want to unlock Button, when user scroll to the end of document. I create PDFView like:

import PDFKit
import SwiftUI

struct PDFDocumentView: UIViewRepresentable {

    private var url: URL?
    private let pdfDocumentDelegate: PdfDocumentDelegate = PdfDocumentDelegate()

    init(url: URL) {
        self.url = url
    }

    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        if let url {
            pdfView.document = PDFDocument(url: url)
            pdfView.autoScales = true
            pdfView.setNeedsLayout()
            pdfView.layoutIfNeeded()
            pdfView.maxScaleFactor = 3.0
            pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
        }
        return pdfView
    }
}

Solution

  • As far as I know, PDFView doesn't have a public API to do this.

    If you have control over the PDF file you show, you can add a very small (in terms of page height) page at the end of the document, observe the PDFViewVisiblePagesChanged notification, and check pdfView.visiblePages contains the last page of pdfView.document.

    NotificationCenter.default.publisher(for: .PDFViewVisiblePagesChanged, object: pdfView)
        .sink { notification in
            if let pdfView = notification.object as? PDFView,
               let doc = pdfView.document,
               let lastPage = doc.page(at: doc.pageCount - 1),
               pdfView.visiblePages.contains(lastPage)  {
                print("Reached bottom!")
            }
        }
        .store(in: &cancellables)
    

    Otherwise, you can try to find a UIScrollView in the view hierarchy of the PDFView, though this is not documented.

    With this scroll view, you can either implement its delegate methods, scrollViewDidScroll or scrollViewDidEndDecelerating depending on your preference (see this answer), or use KVO to observe changes to contentOffset (see this answer).

    After you are able to detect scrolling, you can check if the scroll view is at the bottom by checking contentOffset (see this answer).

    Here is an example:

    var pdfView: PDFView!
    
    override func viewDidLoad() {
        pdfView = PDFView(...)
        // configure pdfView however you want...
        pdfView.document = ...
        // on iOS 17 at least, subviews[0] is the scroll view
        // to be more future proof, consider searching recursively
        scrollViewDelegate = (pdfView.subviews[0] as? UIScrollView)?.delegate
        (pdfView.subviews[0] as? UIScrollView)?.delegate = self
    }
    
    var scrollViewDelegate: UIScrollViewDelegate?
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        scrollViewDelegate?.scrollViewDidEndDecelerating?(scrollView)
        if scrollView.isAtBottom {
            print("Reached bottom!")
        }
    }
    
    // you should implement all the other UIScrollViewDelegate methods like this,
    // in case PDFView needs them
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        scrollViewDelegate?.scrollViewDidScroll?(scrollView)
    }
    
    func scrollViewDidZoom(_ scrollView: UIScrollView) {
        scrollViewDelegate?.scrollViewDidZoom?(scrollView)
    }
    

    UIScrollView.isAtBottom is from the linked answer:

    extension UIScrollView {
        var isAtBottom: Bool {
            return contentOffset.y >= verticalOffsetForBottom
        }
        
        var verticalOffsetForBottom: CGFloat {
            let scrollViewHeight = bounds.height
            let scrollContentSizeHeight = contentSize.height
            let bottomInset = contentInset.bottom
            let scrollViewBottomOffset = scrollContentSizeHeight + bottomInset - scrollViewHeight
            return scrollViewBottomOffset
        }
    }