Learn Courses My Dashboard

PDF Reader with Variable File Names and Start Pages

For reading pdf files, I got some very good coding ideas from another thread. My routine works fine for displaying a fixed file name, e.g. MyFile.pdf.

But I want to implement a general PDF reader, and I’m having a difficult time passing a variable file name into the PDFUIView structure.

How can I pass a different file name to the reader?

How can I set the reader to start with a pdf page number (variable) other than 0?

This is the Nav link I use to call the reader from the Main view. It’s within a NavigationView, so works ok.

NavigationLink {
     PDFUIView()
} label: {
     Image(systemName: "doc.richtext")
}

This is the reader I set up using the coding based on the other thread.

import SwiftUI
import PDFKit

struct PDFUIView: View {
    let pdfDoc: PDFDocument
    var pdfName = "MyFile"
    init() {
        let url = Bundle.main.url(forResource: pdfName, withExtension: "pdf")!
        pdfDoc = PDFDocument(url: url)!
    }
    var body: some View {
        PDFKitView(showing: pdfDoc)
    }
}
struct PDFKitView: UIViewRepresentable {
    let pdfDocument: PDFDocument
    init(showing pdfDoc: PDFDocument) {
        self.pdfDocument = pdfDoc
    }
    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        pdfView.document = pdfDocument
        pdfView.autoScales = true
        return pdfView
    }
    func updateUIView(_ pdfView: PDFView, context: Context) {
        pdfView.document = pdfDocument
    }
}

What have you tried?

  1. struct PDFUIView(name:String) > “Top-level statement cannot begin with a closure expression”
  2. mis-using userDefault:
                       NavigationLink {
                            UserDefaults.standard.set("pdfFileName", forKey: "pdfName")
                            PDFUIView()
                        } label: {
                            SFSymbol.info
                        }

This produces: “Type ‘Void’ cannot conform to ‘View’”

  1. Also tried @State var with @Binding. “‘self’ used before all stored properties are initialized”

So I’m stumped. There must be an easy way to do this, but can’t find it so far.

Well, a lot of it would depend on exactly how you are having your users select a document to display. Without more info from your code, I can’t really say. But, basically, you would create a PDFDocument from whatever the user selects and then pass it into the UIViewRepresentable-wrapped PDFView.

Here is an updated example showing how to pass in a PDFDocument and set the page to view.

import SwiftUI
import PDFKit

struct TestPDFView: View {
    let pdfDoc: PDFDocument
    
    init() {
        //this is just for example
        //in your app, you would get a document from the user selection somehow
        let url = Bundle.main.url(forResource: "Lipsum", withExtension: "pdf")!
        //however you get the document from the user,
        //convert it into a PDFDocument
        pdfDoc = PDFDocument(url: url)!
    }
    
    var body: some View {
        NavigationView {
            List {
                Text("Total Page Count: \(pdfDoc.pageCount)")
                ForEach(1...pdfDoc.pageCount, id: \.self) { pg in
                    NavigationLink("Page \(pg)") {
                        PDFUIView(document: pdfDoc, atPage: pg)
                    }
                }
            }
        }
    }
}

struct PDFUIView: View {
    let document: PDFDocument
    
    @State private var currentPage: Int
    
    init(document: PDFDocument, atPage: Int = 1) {
        self.document = document
        _currentPage = State(initialValue: atPage)
    }
    
    var body: some View {
        PDFKitView(showing: document, atPage: $currentPage)
            .toolbar {
                ToolbarItemGroup(placement: .bottomBar) {
                    Button {
                        currentPage -= 1
                    } label: {
                        Image(systemName: "chevron.left")
                    }
                    .disabled(currentPage == 1)
                    
                    Button {
                        currentPage += 1
                    } label: {
                        Image(systemName: "chevron.right")
                    }
                    .disabled(currentPage == document.pageCount)
                }
            }
            .navigationTitle("Page \(currentPage) of \(document.pageCount)")
            .navigationBarTitleDisplayMode(.inline)
    }
}

struct PDFKitView: UIViewRepresentable {
    let pdfDocument: PDFDocument
    let startPage: Binding<Int>
    
    init(showing pdfDoc: PDFDocument, atPage: Binding<Int>) {
        self.pdfDocument = pdfDoc
        self.startPage = atPage
    }
    
    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        pdfView.autoScales = true
        pdfView.displayMode = .singlePage
        pdfView.document = pdfDocument
        
        //we have to subtract 1 from startPage because page in PDFKit is 0-based
        if let page = pdfDocument.page(at: startPage.wrappedValue - 1) {
            pdfView.go(to: page)
        }
        
        return pdfView
    }
    
    func updateUIView(_ pdfView: PDFView, context: Context) {
        pdfView.document = pdfDocument

        //we have to subtract 1 from startPage because page in PDFKit is 0-based
        if let page = pdfDocument.page(at: startPage.wrappedValue - 1) {
            pdfView.go(to: page)
        }
    }
}
1 Like

Many thanks for the input. I had been using poor architecture and methods. I used your code with minor cosmetic modifications and got it going.
So far, I implemented an information manual in pdf with sections applicable to different views. The info buttons on each view open the manual to the applicable page. This is the initial use of the process, and it has a practical benefit.
But the next step is creation, management, and viewing of pdf reports generated within the app, which is the real reason I want to set this up. Will continue to work on this process, which is easier now that I have your framework. Thanks again for getting me over this snag.

1 Like