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
Simple Observable Struct with Rxswift
Expression Pattern of Type 'String' Cannot Match Values of Type 'Nsstoryboardsegue.Identifier
Instance Member Cannot Be Used on Type | Closures
Searchbar Problem While Trying to Search Firestore and Reload the Tableview
Differences Generic Protocol Type Parameter VS Direct Protocol Type
Shows the Alert When Uitextfield's Are Full or Empty Swift
Crop/Mask Circular Image Node in Sprite Kit Gives Jagged Edges
Retrieving an Array from Firebase
Given a Function Parameter of Type [Int]; Can It Be Constrained to Not Be Empty
Difference Between Optional and Forced Unwrapping
Decode Dictionary with Random Initial Key
Why Do We Need to Set Delegate to Self? Why Isn't It Defaulted by the Compiler
Response Struct Does Not Like Codingkeys
Making Button Span Across VStack
Set Insets to the Collectionview Programmatically in Swift
Type of Optionals Cannot Be Inferred Correctly in Swift 2.2