Search code examples
swiftswiftuizoomingpdfkitapple-pdfkit

PDFKit's scaleFactorForSizeToFit isn't working to set zoom in SwiftUI (UIViewRepresentable)


I'm working on an app that displays a PDF using PDFKit, and I need to be able to set the minimum zoom level - otherwise the user can just zoom out forever. I've tried to set minScaleFactor and maxScaleFactor, and because these turn off autoScales, I need to set the scaleFactor to pdfView.scaleFactorForSizeToFit. However, this setting doesn't result in the same beginning zoom as autoScales and despite changing the actual scaleFactor number, the beginning zoom doesn't change. This photo is with autoScales on: [![image with autoscales on][1]][1]

and then what happens when I use the scaleFactorForSizeToFit: [![image with scaleFactorForSizeToFit][2]][2]

To quote the apple documentation for scaleFactorForSizeToFit, this is the

"size to fit" scale factor that autoScales would use for scaling the current document and layout.

I've pasted my code below. Thank you for your help.

import PDFKit
import SwiftUI
import Combine

class DataLoader : ObservableObject {
    @Published var data : Data?
    var cancellable : AnyCancellable?
    
    func loadUrl(url: URL) {
        cancellable = URLSession.shared.dataTaskPublisher(for: url)
            .map { $0.data }
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { (completion) in
                switch completion {
                case .failure(let failureType):
                    print(failureType)
                    //handle potential errors here
                case .finished:
                    break
                }
        }, receiveValue: { (data) in
            self.data = data
        })
    }
}

struct PDFSwiftUIView : View {
    @StateObject private var dataLoader = DataLoader()
    var StringToBeLoaded: String
    
    var body: some View {
        VStack {
            if let data = dataLoader.data {
                PDFRepresentedView(data: data)
                    .navigationBarHidden(false)
            } else {
                CustomProgressView()
                   //.navigationBarHidden(true)
            }
        }.onAppear {
            dataLoader.loadUrl(url: URL(string: StringToBeLoaded)!)
        }
    }
}

struct PDFRepresentedView: UIViewRepresentable {
    typealias UIViewType = PDFView
    
    let data: Data
    let singlePage: Bool = false
    
    func makeUIView(context _: UIViewRepresentableContext<PDFRepresentedView>) -> UIViewType {
        let pdfView = PDFView()
        
        
     
       // pdfView.autoScales = true
       // pdfView.maxScaleFactor = 0.1
      
        pdfView.minScaleFactor = 1
        pdfView.scaleFactor = pdfView.scaleFactorForSizeToFit
        pdfView.maxScaleFactor = 10
        
       
        if singlePage {
            pdfView.displayMode = .singlePage
        }
        return pdfView
    }
    
    func updateUIView(_ pdfView: UIViewType, context: UIViewRepresentableContext<PDFRepresentedView>) {
        pdfView.document = PDFDocument(data: data)
    }
    func canZoomIn() -> Bool {
           return false
       }
}


struct ContentV_Previews: PreviewProvider {
    static var previews: some View {
        PDFSwiftUIView(StringToBeLoaded: "EXAMPLE_STRING")
            .previewInterfaceOrientation(.portrait)
    }
}


Solution

  • I was eventually able to solve this. The following code is how I managed to solve it:

     if let document = PDFDocument(data: data) {
                   pdfView.displayDirection = .vertical
                   pdfView.autoScales = true
                   pdfView.document = document
                   pdfView.setNeedsLayout()
                   pdfView.layoutIfNeeded()
                pdfView.minScaleFactor = UIScreen.main.bounds.height * 0.00075
                   pdfView.maxScaleFactor = 5.0
              
               }
    

    For some reason, the pdfView.scaleFactorForSizeToFit doesn't work - it always returns 0. This might be an iOS 15 issue - I noticed in another answer that someone else had the same issue. In the code above, what I did was I just scaled the PDF to fit the screen by screen height. This allowed me to more or less "autoscale" on my own. The code above autoscales the PDF correctly and prevents the user from zooming out too far.