QuickLook consumer as a delegate from an NSViewCon

2019-04-24 10:58发布

问题:

I am having some problems implementing QuickLook functionality from a table in an NSView. The limited documentation on QuickLook really doesn't help at all.

After reading through the Apple Docs (which are geared heavily towards custom generators and plugins), I ended up looking at the QuickLookDownloader sample code. This code is based upon a document-based application, but appears to be the right method for me (after all it is Apple's code and it does work in their project).

In my implementation I can get the QuickLook panel to show up just fine, and I can dismiss it just as easy. However, the panel itself never calls the delegate methods from within my NSViewController. As a result I never even get to displaying objects, just the wording "No items selected". And I am stumped.

I tried calling a setDelegate, but get warned about impending doom if I continue down that route...

[QL] QLError(): -[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon. See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.

And then doom happens anyway with a dealloc when trying to respond to one of the delegate methods.

And yes I did read the header which confirms that I should be setting the delegate after I won the panel (see code below).

So here's my code, which pretty much matches the sample code with the exception of a) where I get my data from (I get it from an NSArrayController) and the b) where I get my preview item from (mine comes directly from my model object - or should anyway)

@interface MyViewController : NSViewController 
    <QLPreviewPanelDataSource, QLPreviewPanelDelegate> {

    QLPreviewPanel * previewPanel;
    NSArrayController * myArrayController;
    NSTableView * myTable;

    // [...] Other instance vars
}

@implementation MyViewController

// [...] all the other methods, init, dealloc etc...

-(IBAction)togglePreviewPanel:(id)previewPanel {

    if ([QLPreviewPanel sharedPreviewPanelExists] && 
          [[QLPreviewPanel sharedPreviewPanel] isVisible])
    {
       [[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
    }
    else
    {
       [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
    }
 }

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

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

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

    if (DEBUG) NSLog(@"QuickLook panel control did BEGIN");

    previewPanel = [panel retain];
    panel.delegate = self;
    panel.dataSource = self;
 }

 // This document loses its responsisibility on the preview panel. 
 // Until the next call to -beginPreviewPanelControl: it must not change 
 // the panel's delegate, data source or refresh it.

 -(void)endPreviewPanelControl:(QLPreviewPanel *)panel   
 {
     [previewPanel release];
     previewPanel = nil;

     if (DEBUG) NSLog(@"QuickLook panel control did END");
 }

 // Quick Look panel data source

 -(NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel 
 {

     if (DEBUG) NSLog(@"QuickLook preview count called");

     return [[myArrayController selectedObjects] count];
 }

 -(id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel
        previewItemAtIndex:(NSInteger)index
 {

     if (DEBUG) NSLog(@"QuickLook preview selection of item called");

     return [[displayAC selectedObjects] objectAtIndex:index];
 }

 -(BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event {

    if (DEBUG) NSLog(@"QuickLook panel error handler called");

// redirect all key down events to the table view

    if ([event type] == NSKeyDown) {
        [myTable keyDown:event];
    return YES;
    }

    return NO;
}

The issue seems to be that the acceptsPreviewPanelControl never gets called, so the delegates never get used (they definitely never get called).

I'm sure this is a simple step that I'm missing, but after dissecting the sample code and scouring over the docs I don't see the answer.

Is it because this is all from within an NSViewController (although I see no reason why that should even come into the equation)?

Any and all help much appreciated.

SOLUTION UPDATE

Thanks to Peter's observation, the fix was a quick one. Don't you hate it when the error message in the debugger means what it says? :-)

In my class that loaded MyViewController I simply needed to add three lines of code to fix the problem.

// mainWindow is an IBOutlet to my window because the calling class 
// is a simple object and not an NSWindowController otherwise I could
// have used `self` instead of `mainWindow`

NSResponder * aNextResponder = [mainWindow nextResponder];

[mainWindow setNextResponder:myViewControllerInstance];
[myViewControllerInstance setNextResponder:aNextResponder];

Job done :-) Thanks Peter.

回答1:

Why would you expect it to send you delegate messages if you aren't (yet) its delegate? If you want it to send you delegate messages, then you need to set yourself as its delegate.

I tried calling a setDelegate, but get warned about impending doom if I continue down that route...

[QL] QLError(): -[QLPreviewPanel setDelegate:] called while the panel has no controller - Fix this or this will raise soon. See comments in QLPreviewPanel.h for -acceptsPreviewPanelControl:/-beginPreviewPanelControl:/-endPreviewPanelControl:.

“No controller”, it says. So, you need it to have a controller.

The comments on that header, particularly on acceptsPreviewPanelControl: and the QLPreviewPanel instance method updateController, suggest that the panel's controller, when it has one, is an object that is in the responder chain. Therefore, if your controller is not becoming the panel's controller, it's because your controller isn't in the responder chain.

So, fix that, and then it'll work.

I would imagine that your view controller should be in the responder chain whenever its view or any subview thereof is in the responder chain, but maybe this isn't the case. The documentation doesn't say. If all else fails, set yourself as some view's next responder explicitly (and its previous next responder as your next responder), then send the preview panel an updateController message.