Use Quick Look Inside a Swift Cocoa Application to Preview Audio Files

Use Quick Look inside a Swift cocoa application to preview audio files

This turned out to be pretty simple actually. All the APIs are public. I do think things got simpler with OS X 10.10, just not super well documented maybe?

Here is an example of a ViewController that has a single button that triggers the standard Quick Look Panel with two items that can be found on the filesystem.

class ViewController: NSViewController {
@IBAction func showQuickLookPanel(sender: AnyObject) {
if let panel = QLPreviewPanel.sharedPreviewPanel() {
panel.dataSource = self
panel.makeKeyAndOrderFront(self)
}
}
}

extension ViewController: QLPreviewPanelDataSource {
func numberOfPreviewItemsInPreviewPanel(panel: QLPreviewPanel!) -> Int {
return 2
}

func previewPanel(panel: QLPreviewPanel!, previewItemAtIndex index: Int) -> QLPreviewItem! {
if index == 0 {
return NSURL(fileURLWithPath: "/Library/Desktop Pictures/Beach.jpg")
} else {
return NSURL(fileURLWithPath: "/System/Library/Compositions/Rollercoaster.mov")
}
}
}

There are three parts to this.

First, to open the standard Quick Look window just call makeKeyAndOrderFront on the shared panel.

The panel knows what to show because it talks to it's datasource, which is implemented via QLPreviewPanelDataSource. As you can see in my example it simply returns a count of 2 and it can be asked to return an object that implements QLPreviewItem.

For my demo I simply returns NSURL instances to two resources that are included in the system by default. Turns out that NSURL already implements the QLPreviewItem protocol so there is nothing extra to do.

If the items that you want to preview do not easily translate to URLs (files) on the filesystem then you will need to do a more complicated implementation of a QLPreviewItem object.

I bet that pointing to MP3 files will just work fine.

Quick look preview only shows a blurred 1st page of documents in MacOS

I finally found a working solution to this problem.The trick is to display the quicklook preview in a navigationViewController.
here is the code:

struct PreviewControllerMac: UIViewControllerRepresentable {
@Binding var url: URL

func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.delegate = context.coordinator
let navigationController = UINavigationController(rootViewController: controller)
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, QLPreviewControllerDelegate, QLPreviewControllerDataSource {
let parent: PreviewControllerMac
init(parent: PreviewControllerMac) {
self.parent = parent
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return parent.url as NSURL
}
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
return .updateContents
}
}

How to use SpaceBar button inside an application to invoke quick look

On your view, do this:

- (void)keyDown:(NSEvent *)event
{
unichar firstChar = 0;
if ([[event charactersIgnoringModifiers] length] > 0)
firstChar = [[event charactersIgnoringModifiers] characterAtIndex:0];

if (firstChar == ' ')
{
if ([QLPreviewPanel sharedPreviewPanelExists]
&& [[QLPreviewPanel sharedPreviewPanel] isVisible])
{
[[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
}
else
{
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
[[NSApp mainWindow] makeKeyWindow];
}
}
else if (firstChar == NSRightArrowFunctionKey)
{
if ([QLPreviewPanel sharedPreviewPanelExists]
&& [[QLPreviewPanel sharedPreviewPanel] isVisible])
{
[[QLPreviewPanel sharedPreviewPanel] selectNextItem];
return;
}
}
else if (firstChar == NSLeftArrowFunctionKey)
{
if ([QLPreviewPanel sharedPreviewPanelExists]
&& [[QLPreviewPanel sharedPreviewPanel] isVisible])
{
[[QLPreviewPanel sharedPreviewPanel] selectPreviousItem];
return;
}
}
else
[super keyDown:event];
}

Then, I do this in my app's delegate (AppDelegate.m):

- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
{
//note that this methods indeed gets called because NSApp's
//delegate is in the responder chain.
return YES;
}

- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{
previewPanel = panel; //set an ivar
[panel setDataSource:self];
}

- (void)endPreviewPanelControl:(QLPreviewPanel *)panel
{
previewPanel = nil;
}

- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
{
//return a number of your choice (depends on your own app)
}

- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel
previewItemAtIndex:(NSInteger)index
{
//return an object of your choice (depends on your app)
}

- (void)handleCurrentFileItemsSelectionChange:(NSNotification *)note
{
[previewPanel reloadData]; //referring to the ivar
}

Cocoa QuickLook initiated by NSTableView Cell

The documentation isn't the best for this, since it's a new feature that was added in 10.6. (Well, there is obviously the class and protocol references, but in my experience, I've always found the Companion Guides to be more helpful in understanding how the objects are intended to be used in a real-world scenario).

The QLPreviewPanelController Protocol Reference defines 3 methods:

QLPreviewPanelController Protocol Reference

The Quick Look preview panel shows previews for items provided by the first object in the responder chain that implements the methods in this protocol. You typically implement these methods in your window controller or delegate. You should never try to modify preview panel state if you’re not controlling the panel.

- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;

- (BOOL)beginPreviewPanelControl:(QLPreviewPanel *)panel;

- (void)endPreviewPanelControl:(QLPreviewPanel *)panel;

I'm guessing that your code should look like this:

- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel
{
return YES;
}

You shouldn't be doing anything in that method besides returning YES. acceptsPreviewPanelControl: is sent to every object in the responder chain until something returns YES. By returning YES, that object effectively becomes "the controller". The latter 2 methods are called on the controller object after it returns YES from the first method. So you should only be setting the delegate and datasource in the beginPreviewPanelControl: method (at which time you will be regarded as the current controller).

- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
{

// This document is now responsible of the preview panel
// It is allowed to set the delegate, data source and refresh panel.

[QLPreviewPanel sharedPreviewPanel].delegate = self;
[QLPreviewPanel sharedPreviewPanel].dataSource = self;

NSLog(@"We can now receive QL Events.");
}


Related Topics



Leave a reply



Submit