Quick Look with NStableView and Swift

So, I have a macOS project I’m currently working on where I wanted to add some polish to the app in the form of Quick Look file previews. Im building the app with Swift, and couldn’t find any nice examples of doing this. All of the samples I found online are written in Objective-C. The implementation is not at all difficult, but I figured, since I hadn’t posted in a while - I thought write a quick blog post. So here we are. 🙂

I won’t go into any real depth, as most of the code is reasonable self explanatory. I just wanted to post the code I used to get up and running.

Setup

I guess I’m a little old school, but I still like to use xib’s as opposed to storyboards. So my project is a pretty basic setup. The AppDelegate, a ViewController and the MainMenu.xib with an NSTableView.

Implementation

First up we need to define our datasource array, and the preview panel in our ViewController:

ViewController.swift
var previewPanel: QLPreviewPanel?
var fileUrls: [URL]?

And here’s my ViewController extension, which includes the Quick Look datasource and delegate functions. We generate the preview icon using the pathExtension from our url and passing that to the NSWorkSpace: .icon(forFileType: Sting) function:

Quicklook.swift
import Quartz

extension ViewController : QLPreviewPanelDelegate, QLPreviewPanelDataSource {
    override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
        return true
    }
    
    override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
        previewPanel = panel
        
        panel.delegate = self
        panel.dataSource = self
    }
    
    override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
        previewPanel = nil
    }
    
    
    // MARK: - QLPreviewPanelDataSource
    func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {
        return 1
    }
        
    func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {
        let fileUrl = fileUrls[index]
        return fileURL as QLPreviewItem
    }
    
    
    // MARK: - QLPreviewPanelDataSource
    func previewPanel(_ panel: QLPreviewPanel!, handle event: NSEvent!) -> Bool {
        if event.type == .keyDown {
            tableView.keyDown(with: event)
            return true
        }
        return false
    }
    
    func previewPanel(_ panel: QLPreviewPanel!, sourceFrameOnScreenFor item: QLPreviewItem!) -> NSRect {
        var returnIconRect = NSZeroRect
        let index: Int = arrayController.selectionIndex
        if index != NSNotFound {
            var iconRect = tableView.frameOfCell(atColumn: 0, row: index)
            
            //fix icon width
            iconRect.size.width = iconRect.size.height
            let visibleRect = tableView.visibleRect
            
            if NSIntersectsRect(visibleRect, iconRect) {
                var convertedRect = tableView.convert(iconRect, to: nil)
                convertedRect.origin = (tableView.window?.convertPoint(toScreen: convertedRect.origin))!
                returnIconRect = convertedRect
            }
        }
        return returnIconRect
    }
    
    func previewPanel(_ panel: QLPreviewPanel!, transitionImageFor item: QLPreviewItem!, contentRect: UnsafeMutablePointer<NSRect>!) -> Any! {
        let fileUrl = fileUrls[index]
        let fileType = fileUrl.pathExtension
        let image = NSWorkspace.shared.icon(forFileType: fileType)
        
        return image
    }
    
    func togglePreviewPanel() {
        
        if QLPreviewPanel.sharedPreviewPanelExists() && QLPreviewPanel.shared().isVisible {
                QLPreviewPanel.shared().orderOut(nil)
            } else {
                QLPreviewPanel.shared().makeKeyAndOrderFront(nil)
        }
    }
}

We then need to subclass the tableView, so we capture any space bar keyDown events, which we use to toggle the Quick Look preview panel.

QuicKLookTableView.swift
import Cocoa

class QuicKLookTableView : NSTableView {
    
    @IBOutlet var viewController: ViewController!
    
    override open func keyDown(with event: NSEvent) {
        let key = event.charactersIgnoringModifiers
        if key == " " {
            viewController.togglePreviewPanel()
        }
        
        super.keyDown(with: event)
    }
}

Don’t forget to add the subclass to our tableView: Apply our custom subclass to the tableView Apply our custom subclass to the tableView

And also connect the ViewController outlet in interface builder: Connect the ViewController outlet Connect the ViewController outlet

And that is pretty much all there is to it, quite simple really. 🚀