Search code examples
swiftswiftuiquicklook

How to use NSOpenPanel to select a file for QLPreviewView?


Thanks to this answer i can see QuickLook preview of a pdf embedded in my swiftui view.

But it's only to preview the pdf that's stored in the app bundle resource. How do i use NSOpenPanel to choose a file and display it in the QLPreviewView?

import SwiftUI
import AppKit
import Quartz

func loadPreviewItem(with name: String) -> NSURL {

    let file = name.components(separatedBy: ".")
    let path = Bundle.main.path(forResource: file.first!, ofType: file.last!)
    let url = NSURL(fileURLWithPath: path ?? "")
    print(url)
    return url
}

struct MyPreview: NSViewRepresentable {
    var fileName: String

    func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
        let preview = QLPreviewView(frame: .zero, style: .normal)
        preview?.autostarts = true
        preview?.previewItem = loadPreviewItem(with: fileName) as QLPreviewItem
        return preview ?? QLPreviewView()
    }

    func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
    }

    typealias NSViewType = QLPreviewView

}

struct ContentView: View {



    var body: some View {

        // example.pdf is expected in app bundle resources
        VStack {
            MyPreview(fileName: "testing.pdf")
            Divider()
        }
        
        Button("Select PDF") {

            let openPanel = NSOpenPanel()
            openPanel.allowedFileTypes = ["pdf"]
            openPanel.allowsMultipleSelection = false
            openPanel.canChooseDirectories = false
            openPanel.canChooseFiles = true
            openPanel.runModal()
         
            
        }
    }
}

*UPDATE

This is what i've tried but nothing happens after i choose a pdf file with the openpanel. The View is blank. I think there's something that i haven't done correctly in the updateNSView.

struct ContentView: View {
@State var filename = ""

var body: some View {
    VStack {
        MyPreview(fileName: filename)
        Divider()
    }
    
    Button("Select PDF") {

        let openPanel = NSOpenPanel()
        openPanel.allowedFileTypes = ["pdf"]
        openPanel.allowsMultipleSelection = false
        openPanel.canChooseDirectories = false
        openPanel.canChooseFiles = true
        openPanel.runModal()
        print(openPanel.url!.lastPathComponent)
        filename = openPanel.url!.lastPathComponent
        //
        
    }
    }
}


struct MyPreview: NSViewRepresentable {
    var fileName: String

    func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
        let preview = QLPreviewView(frame: .zero, style: .normal)
        preview?.autostarts = true
        preview?.previewItem = loadPreviewItem(with: fileName) as QLPreviewItem
        return preview ?? QLPreviewView()
    }

    func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
        
        let preview = QLPreviewView(frame: .zero, style: .normal)
        preview?.refreshPreviewItem()
        
    }

    typealias NSViewType = QLPreviewView

}

*UPDATE 2

My latest attempt using now a model to update the file url after choosing one with the open panel but nothing still happens.

It updates pdfurl successfully with the file url but the QLPreviewView doesn't update with the changes in updateNSView. I'm using the refreshItemPreview() which should work but i'm not sure what i'm doing wrong here

class PDFViewModel: ObservableObject {
    @Published var pdfurl = ""
}


struct MyPreview: NSViewRepresentable {
    
    @ObservedObject var pdfVM = PDFViewModel()

    func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
        let preview = QLPreviewView(frame: .zero, style: .normal)
        preview?.previewItem =  NSURL(string: pdfVM.pdfurl)
    
        return preview ?? QLPreviewView()
        
        
    }

    func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>)  {

        let preview = QLPreviewView(frame: .zero, style: .normal)

        preview?.refreshPreviewItem()


    }
    
    

    typealias NSViewType = QLPreviewView

}
    
    


struct ContentView: View {
    
    
    @ObservedObject var pdfVM = PDFViewModel()

    var body: some View {
        VStack {
            MyPreview()
            Divider()
        }
        
        Button("Select PDF") {
            
               let openPanel = NSOpenPanel()
                    openPanel.allowedFileTypes = ["pdf"]
                    openPanel.allowsMultipleSelection = false
                    openPanel.canChooseDirectories = false
                    openPanel.canChooseFiles = true
                    openPanel.runModal()
            
            pdfVM.pdfurl = "\(openPanel.url!)"
        
          print("the url is now \(pdfVM.pdfurl)")

            }
  
    }

}

Solution

  • Hacky solution but it works....

    I used a looped timer that monitors and sets the preview item url which is also now stored with AppStorage

    class PDFViewModel: ObservableObject {
        @AppStorage("pdfurl") var pdfurl: String = ""
    }
    
    
    struct MyPreview: NSViewRepresentable {
        
        @ObservedObject var pdfVM = PDFViewModel()
        let preview = QLPreviewView(frame: .zero, style: .normal)
    
      
        func makeNSView(context: NSViewRepresentableContext<MyPreview>) -> QLPreviewView {
            preview?.autostarts = true
       
            preview?.shouldCloseWithWindow = false
            preview?.previewItem =  NSURL(string: pdfVM.pdfurl)
            updatedocument()
        
            return preview ?? QLPreviewView()
            
            
        }
        
        
        func updatedocument() {
            Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
         
                let delay = DispatchTime.now() + 0.3
                        DispatchQueue.main.asyncAfter(deadline: delay, execute: {
                            preview?.previewItem =  NSURL(string: pdfVM.pdfurl)
                        })
            }
        }
        
         
    
        func updateNSView(_ nsView: QLPreviewView, context: NSViewRepresentableContext<MyPreview>) {
    
    
           }
        
        typealias NSViewType = QLPreviewView
    
    }
        
        
    
    
    struct ContentView: View {
    
        @ObservedObject var pdfVM = PDFViewModel()
    
        var body: some View {
            VStack {
                MyPreview()
                Divider()
            }
    
    
            Button("Select Document") {
                
                   let openPanel = NSOpenPanel()
                        openPanel.allowedFileTypes = ["pdf", "jpg","doc","pptx", "png", "xls", "xlsx", "docx", "jpeg", "txt"]
                        openPanel.allowsMultipleSelection = false
                        openPanel.canChooseDirectories = false
                        openPanel.canChooseFiles = true
                        openPanel.begin { (result) in
                    if result == NSApplication.ModalResponse.OK {
                        if let url = openPanel.url {
                            pdfVM.pdfurl = "\(url)"
                        }
                    } else if result == NSApplication.ModalResponse.cancel {
    
                    }
                    }
    
                }
            
            
            
      
        }
    
    
    }