Search code examples
swiftpdfswiftuipdfviewios-pdfkit

Is there any way where we can get the current page number in PDFView and use it in SwiftUI


I am making an app pdf reader using PDFKit but I am unable to get the current page number. I can get the total pages by pdfView.document?.pageCount. The alternative way we can use for this is to change the page by button and count it but I want the PDFView default feature Changing the page by Swipe by sitting the pdfView.usePageViewController(true) but it does not give any method to get the current page number

Code

struct ContentView: View {
    let url = Bundle.main.url(forResource: "file", withExtension: "pdf")
    var body: some View {
        VStack{
            PDFKitRepresentedView(data: try! Data(contentsOf: url!))
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
import PDFKit
import SwiftUI


struct PDFKitRepresentedView: UIViewRepresentable {
    typealias UIViewType = PDFView
    let data: Data
    func makeUIView(context _: UIViewRepresentableContext<PDFKitRepresentedView>) -> UIViewType {
        // Create a `PDFView` and set its `PDFDocument`.
        let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
        pdfView.document = PDFDocument(data: data)
        pdfView.backgroundColor = UIColor.red
        pdfView.displayMode = .singlePage
        pdfView.displayDirection = .horizontal
        pdfView.usePageViewController(true)
        pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
        pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
        pdfView.autoScales = true
        
        return pdfView
    }
    func updateUIView(_ pdfView: UIViewType, context _: UIViewRepresentableContext<PDFKitRepresentedView>) {
        pdfView.document = PDFDocument(data: data)
    }
}

Update

According to suggestion given by workingdog support Ukraine below the coordinator class printing the result but when I use Binding to pass the currentPage to SwiftUI its not working the page number is not updating in UI and on swiping its repeating the first two pages only

New Updated code

struct ContentView: View {
    @State var currentPage = -1
    @State var totalPages :Int?
    let url = Bundle.main.url(forResource: "file", withExtension: "pdf")
    var body: some View {
        VStack{
            HStack{
                Text("\(currentPage)/")
                Text("\(totalPages ?? 0)")
            }
            if let url = url {
                PDFKitRepresentedView(data:try! Data(contentsOf: url),totalPages: $totalPages,currentPage: $currentPage)
            }
        }
    }
}
struct PDFKitRepresentedView: UIViewRepresentable {
    typealias UIViewType = PDFView
    let data: Data
    @Binding var totalPages:Int?
    @Binding var currentPage :Int
    let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))

    func makeUIView(context: Context) -> UIViewType {
        pdfView.document = PDFDocument(data: data)
        pdfView.backgroundColor = UIColor.red
        pdfView.displayMode = .singlePage
        pdfView.displayDirection = .horizontal
        pdfView.usePageViewController(true)
        pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
        pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
        pdfView.autoScales = true
        pdfView.delegate = context.coordinator
        return pdfView
    }
    
    func updateUIView(_ pdfView: UIViewType, context _: Context) {
        pdfView.document = PDFDocument(data: data)
        DispatchQueue.main.async {
            totalPages = pdfView.document?.pageCount
        }
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator(self, cp: $currentPage)
    }
        
    class Coordinator: NSObject, PDFViewDelegate {
        var parent: PDFKitRepresentedView
        @Binding var currentPage :Int
        
        init(_ parent: PDFKitRepresentedView,cp:Binding<Int>) {
            self.parent = parent
            _currentPage = cp
            super.init()
            NotificationCenter.default.addObserver(self, selector: #selector(pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
        }

        @objc func pageChangeHandler(_ notification: Notification) {
            
            if let thePage = parent.pdfView.currentPage,
                let ndx = parent.pdfView.document?.index(for: thePage),
               currentPage != ndx {
                DispatchQueue.main.async {
                    self.currentPage = ndx
                }
                print("--------> currentPageIndex: \(ndx) ")
            }
        }
    }
}

Solution

  • According to the docs at: https://developer.apple.com/documentation/pdfkit/pdfview there is a currentPage that returns the current page. You could then use something like this to get the index of it:

    if let thePage = pdfView.currentPage, let ndx = pdfView.document?.index(for: thePage) {
       print("--> currentPageIndex: \(ndx) ")
       // ....
    }
    

    EDIT-1:

    Try the following approach, using a Coordinator class for the PDFViewDelegate and getting notified when a .PDFViewPageChanged with a NotificationCenter.default.addObserver(...).

    You will have to adjust the code for your own purpose.

    struct PDFKitRepresentedView: UIViewRepresentable {
        typealias UIViewType = PDFView
        let data: Data
        let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
    
        func makeUIView(context: Context) -> UIViewType {
            pdfView.document = PDFDocument(data: data)
            pdfView.backgroundColor = UIColor.red
            pdfView.displayMode = .singlePage
            pdfView.displayDirection = .horizontal
            pdfView.usePageViewController(true)
            pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
            pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
            pdfView.autoScales = true
            pdfView.delegate = context.coordinator
    
            return pdfView
        }
        
        func updateUIView(_ pdfView: UIViewType, context _: Context) {
            pdfView.document = PDFDocument(data: data)
        }
        
        func makeCoordinator() -> Coordinator {
            return Coordinator(self)
        }
            
        class Coordinator: NSObject, PDFViewDelegate {
            var parent: PDFKitRepresentedView
            var prevPage = -1
            
            init(_ parent: PDFKitRepresentedView) {
                self.parent = parent
                super.init()
                NotificationCenter.default.addObserver(self, selector: #selector(pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil)
            }
    
            @objc func pageChangeHandler(_ notification: Notification) {
                if let thePage = parent.pdfView.currentPage,
                    let ndx = parent.pdfView.document?.index(for: thePage), 
                    prevPage != ndx {
                    print("--------> currentPageIndex: \(ndx) ")
                    prevPage = ndx
                }
            }
        }
        
    }
    

    EDIT-2:

    To access the currentPage in ContentView, that is, outside the PDFViewer, you can use the following approach. It uses a .onReceive(...) of a page change notification, and some minor changes of the original code.

    struct ContentView: View {
        @State var currentPage = 0 
    
        let pdfViewer: PDFViewer // <--- here
        
        init() {
            if let url = Bundle.main.url(forResource: "file", withExtension: "pdf"),
            let docData = try? Data(contentsOf: url) {
                self.pdfViewer = PDFViewer(data: docData)
           } else {
                self.pdfViewer = PDFViewer(data: Data())
            }
        }
    
        var body: some View {
            VStack {
                Text("page \(currentPage)")
                pdfViewer
                    .onReceive(NotificationCenter.default.publisher(for: .PDFViewPageChanged)) { _ in
                        if let thePage = pdfViewer.pdfView.currentPage,
                           let ndx = pdfViewer.pdfView.document?.index(for: thePage), currentPage != ndx {
                            currentPage = ndx
                        }
                    }
            }
        }
    }
    
    struct PDFViewer: UIViewRepresentable {
        typealias UIViewType = PDFView
        let data: Data
        
        let pdfView = PDFView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
    
        func makeUIView(context: Context) -> UIViewType {
            pdfView.document = PDFDocument(data: data)
            pdfView.backgroundColor = UIColor.red
            pdfView.displayMode = .singlePage
            pdfView.displayDirection = .horizontal
            pdfView.usePageViewController(true)
            pdfView.maxScaleFactor = pdfView.scaleFactorForSizeToFit
            pdfView.minScaleFactor = pdfView.scaleFactorForSizeToFit
            pdfView.autoScales = true
    
            return pdfView
        }
        
        func updateUIView(_ pdfView: UIViewType, context _: Context) {
            pdfView.document = PDFDocument(data: data)
        }
    }