2

Trying to figure out how to work with QuickLook in SwiftUI on both iOS and macOS. I suspect that in far future, there will be some unified SwiftUI QL API, but can’t see it in sight yet, so let’s work with what we have…

How do I present and configure a QLPreviewPanel from my SwiftUI view? So far, I have this:

struct ItemView: View {
    let previewPanelThing = PreviewPanelThing()
    var body: some View {
        Button("OSX preview") {
            print("osx preview")
            if let previewPanel = QLPreviewPanel.shared() {
                self.previewPanelThing.updateControllerForPanel(previewPanel)
                previewPanel.makeKeyAndOrderFront(self.previewPanelThing)
            }                   
        }
    }
}

class PreviewPanelThing: QLPreviewPanelDataSource {

    func updateControllerForPanel(_ panel: QLPreviewPanel) {
        print("updating controller")
        panel.updateController()
    }

    func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
        print("number of items")
        return 1
    }

    func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
        print("requesting preview item")
        let fileURL: URL = Bundle.main.url(forResource: "Thinking-of-getting-a-cat", withExtension: "png")!
        return fileURL as QLPreviewItem
    }   
}

This isn’t working. I suspect this is because the QLPreviewPanel documentation says: The preview panel follows the responder chain and adapts to the first responder willing to control it. My previewPanelThing instance isn’t in the UI and responder chain. I’m not sure how the responder chain works in SwiftUI and how to best go about it.

Jaanus
  • 17,688
  • 15
  • 65
  • 110

1 Answers1

6

Here is possible approach on using QLPreviewView directly to preview PDF files (in this demo stored in main application bundle, but this does not change the common idea)

Update: added variant with QLPreviewPanel on button click

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

    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 {
    let qlCoordinator = QLCoordinator()

    var body: some View {

        // example.pdf is expected in app bundle resources
        VStack {
            MyPreview(fileName: "example.pdf")
            Divider()
            Button("Show panel") {
                let panel = QLPreviewPanel.shared()
                panel?.center()
                panel?.dataSource = self.qlCoordinator
                panel?.makeKeyAndOrderFront(nil)
            }
        }
    }

    class QLCoordinator: NSObject, QLPreviewPanelDataSource {
        func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
            return loadPreviewItem(with: "example.pdf") as QLPreviewItem
        }

        func numberOfPreviewItems(in controller: QLPreviewPanel) -> Int {
            return 1
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 4
    The solution works very well but I get a lot of messages like these: `-[QLPreviewPanel setDataSource:] called while the panel has no controller - Fix this or this will raise soon. See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.`. Do you have any idea how to get rid of them? – Dominik Nov 13 '20 at 16:32
  • @Asperi I'm struggling to see the relationship between `MyPreview(...)` in the SwiftUI view and the `QLPreviewPanel` below it in the `Button` callback. How are those two related? – Clifton Labrum Feb 23 '21 at 00:52
  • 2
    @Dominik I think you can ignore those warnings. I always got those in a pure AppKit implementation of QuickLook, so I don't think you need to be too worried. – Clifton Labrum Feb 23 '21 at 00:53