在 SwiftUI 顯示 PDF

在 SwiftUI 中透過 PDFKit 框架顯示 PDF 文件,需要定義兩個 View 元件,並且匯入 PDFKit 框架,如下:

import PDFKit

第一個 View 元件用來顯示 PDF 內容,並且把 PDFView 實體傳出去,作為後續要顯示縮圖時使用。如果 PDF 有設定密碼(例如1234)程式中自動解鎖,當然把解鎖部分移除就可以讓使用者自行輸入密碼。這個結構應該有更好的寫法,不過目前可 work。

struct DisplayPDF: UIViewRepresentable {
    var filename: String
    @Binding var pdfView: PDFView?
    
    func makeUIView(context: Context) -> PDFView {
        let pdfView = PDFView()
        self.pdfView = pdfView
        return pdfView
    }
    
    func updateUIView(_ uiView: PDFView, context: Context) {
        guard let url = Bundle.main.url(forResource: filename, withExtension: nil) else {
           return
        }
        
        guard let document = PDFDocument(url: url) else {
            return
        }
        
        if document.isEncrypted {
            document.unlock(withPassword: "1234")
        }
        
        uiView.document = document
        uiView.autoScales = true
    }
    
    typealias UIViewType = PDFView
}

第二個 View 元件用來顯示縮圖,若沒有縮圖需求的話,可以省略。

struct DisplayThumbnailPDF: UIViewRepresentable {
    var pdfView: PDFView?
    private let size = CGSize(width: 72, height: 88)
    
    func makeUIView(context: Context) -> PDFThumbnailView {
        let thumbnail = PDFThumbnailView()
        thumbnail.layoutMode = .horizontal
        thumbnail.backgroundColor = .darkGray
        thumbnail.thumbnailSize = size
        return thumbnail
    }
    
    func updateUIView(_ uiView: PDFThumbnailView, context: Context) {
        uiView.pdfView = pdfView
    }
    
    typealias UIViewType = PDFThumbnailView
}

最後主程式,這裡使用 NavigationSplitView 作為排版範例。這裡有兩個小地方比較奇特,比較偏向修正 PDFKit 的內部問題,一個是 onChange 修飾器,目的是讓 PDF 內容切換時,下方的縮圖區大小能夠正常顯示,沒有這個修飾器在模擬器中跑會有問題,但不確定各實機上是否需要這個修飾器。第二個地方在 DisplayThumbnailPDF 後面 frame 修飾器中的寬度值 +2,這是每個縮圖寬度少了 2 點導致縮圖無法正確反應使用者的點選位置。這個值是網路上有人試出來的,請參考這篇文章:https://atomicbird.com/blog/pdfkit-thumbnails/

struct Item: Identifiable {
    var filename: String
    var id: String { filename }
}

struct ContentView: View {
    @State private var selectedId: String?
    @State private var pdfView: PDFView?
    
    private let items: [Item] = [
        .init(filename: "無密碼.pdf"),
        .init(filename: "有密碼.pdf")
    ]
    
    var body: some View {
        NavigationSplitView {
            List(items, selection: $selectedId) { item in
                Text(item.filename)
            }
            .onChange(of: selectedId) { newValue in
                pdfView = nil
            }
        } detail: {
            if let selectedId {
                VStack {
                    DisplayPDF(filename: selectedId, pdfView: $pdfView)
                    ScrollView(.horizontal) {
                        DisplayThumbnailPDF(pdfView: pdfView)
                            .frame(width: (CGFloat(pdfView?.document?.pageCount ?? 0) + 2) * 72, height: 100)
                    }
                }
            }
        }
    }
}

以上就是在 SwiftUI 中透過 PDFKit 框架顯示 PDF 文件的方式。

截圖-2022-11-30-12.54.45

發表迴響