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

991 views Asked by At

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)
    }
}

3

There are 3 answers

0
AudioBubble On BEST ANSWER

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.

1
workingdog support Ukraine On

maybe it is to do with the sequence. This seems to work for me:

    pdfView.scaleFactor = pdfView.scaleFactorForSizeToFit
    pdfView.maxScaleFactor = 10.0
    pdfView.minScaleFactor = 1.0
    pdfView.autoScales = true
0
domi852 On

This last solution works but you are kind of hardcoding the scale factor. so it only works if the page height is always the same. And bear in mind that macOS does not have a UIScreen and even under iPadOS there can be several windows and the window can have a different height than the screen height with the new StageManager... this worked for me:

  • first, wrap your swiftUIView (in the above example PDFRepresentedView) in a geometry reader. pass the view height (proxy.size.height) into the PDFRepresentedView.

  • in makeUIView, set
    pdfView.maxScaleFactor = some value > 1 pdfView.autoScales = true

  • in updateUiView, set:

          let pdfView = uiView
          if let pageHeight = pdfView.currentPage?.bounds(for: .mediaBox).height  {
             let scaleFactor:CGFloat = self.viewHeight / pageHeight
             pdfView.minScaleFactor = scaleFactor
          }
    

Since my app also support macOS, I have written an NSViewRepresentable in the same way. Happy coding!