QuickLook consumer as a delegate from an NSViewController - objective-c

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.

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.

After so many years, in the swift world, I found this line of code works as well.
Without rearrange the default response chain, just "push" your view controller to be the first responder in the window. I'm not sure if it works for every scenario:
view.window?.makeFirstResponder(self)
And the object setups are the same:
override func acceptsPreviewPanelControl(_ panel: QLPreviewPanel!) -> Bool {
return true
}
override func beginPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = self
panel.delegate = self
panel.currentPreviewItemIndex = //your initial index
}
override func endPreviewPanelControl(_ panel: QLPreviewPanel!) {
panel.dataSource = nil
panel.delegate = nil
}

Related

menuWillOpen: and menuDidClose: not invoked for NSMenuDelegate

[Edit] as Willeke helpfully points out it's menuDidClose: NOT menuWillClose:. My code actually had that part right. Correcting the post in case someone else finds this researching a similar problem.
I'm sure this is just a Cocoa newbie problem but I've wracked my brain on it for hours. I've read the NSMenu and NSMenuDelegate docs a few times trying to figure out what I'm missing but it looks straight forward.
I have a window controller for a preferences window with a toolbar and three views. The window controller is declared as NSMenuDelegate.
#interface PrefsController : NSWindowController <NSMenuDelegate, NSWindowDelegate, NSOpenSavePanelDelegate>
This issue is a NSPopUpButton on the first view. The menu associated with popupbutton works fine. I can modify, etc. the menu via the associated IBOutlet variable. It's bound to Shared User Defaults Controller for selected value and that works fine.
But the menuWillOpen: and menuDidClose: methods are not invoked when the menu is accessed.
- (void)menuWillOpen:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(32, 32)];
}
}
- (void)menuDidClose:(NSMenu *)menu {
if (menu == myPopupButton.menu) {
[self updateMenuImages:NSMakeSize(16, 16)];
}
}
My apologies for what is almost certainly a dumb mistake on my part, but I'm stumped.
Menu delegates are not used that often, so Apple hasn't made them too easy to set up in Interface Builder. Instead, do this in awakeFromNib:
myPopupButton.menu.delegate = self;

no methods on NSWindowController subclass are called

I have a window that I am creating based off of clicking a row in a Tableview, instantiated like such:
HKLUserProfileController *userProfileController = [[HKLUserProfileController alloc] initWithNibName:#"HKLUserProfileController" bundle:nil];
_wc =[[NSWindowController alloc] initWithWindowNibName:#"HKLProfileWindowController"];
[_wc.window.contentView addSubview:userProfileController.view];
// other extraneous stuff here
[_wc showWindow:self];
if([_wc.window canBecomeKeyWindow]) {
[_wc.window makeMainWindow];
[NSApp activateIgnoringOtherApps:YES];
[self makeKeyAndOrderFront:self];
}
This works, but I cannot seem to get the window to become the main/key/front window. I've tried:
... from this place where I am creating the WindowController, and also from inside viewDidLoad/loadView of the NSViewController whose View was added to the Window. No dice. (this is my ultimate goal here, so if you see something obvious i'm missing, please point it out).
So I realized that I should be trying to do this in the subclass of the WindowController itself... so I thought to put it in windowDidLoad, but no result. I set some breakpoints, and tried the other logical init methods, and to my surprise, NONE of them fire at all.
#implementation HKLProfileWindowController
- (void)windowDidLoad {
[super windowDidLoad];
// breakpoint here
}
-(void)awakeFromNib {
// breakpoint here
}
- (id)init {
// breakpoint here
self = [super init];
if (self) {
// breakpoint here
}
return self;
}
#end
My xib is connected as an Outlet / Delegate to my File's Owner properly, as far as I can tell. This is leading me to believe this is the root of the problem, but for the life of me, I can't figure out what is the issue...
Thanks...
EDIT
I realize what is happening with the keyWindow - because it's coming from shouldSelectRow in the TableView, it IS becoming key on mouseDown events, but the first window - which holds the TableView - is becoming key again on mouseUp events... which is maddening!
Will search for solutions to that, but open to comments here!
(still can't figure out why the init methods are not firing though...
You use NSWindowController in your alloc/init line and you expect it to become an HKLProfileWindowController instance? Why?
You also need the File's Owner custom class set to HKLProfileWindowController but there's no reason to expect that's sufficient to get an HKLProfileWindowController instance when you ask for an NSWindowController. What you get is an NSWindowController and none of the subclass code is used.
I'll delete this answer if your code is from before you implemented the HKLProfileWindowController class, but given what you have pasted here, that's why your breakpoints don't get hit.

Is there an UIViewController Event that satisfies this properties:

It's called only once, like viewDidLoad
It's called right before viewWillAppear. So it's called after the UIViewController navigationController is no longer empty.
Basically I want to set things up programatically stuffs for UIViewController. However, I want navigationController property to already exist. Also I want the whole thing to be called only once.
I'm not super-familiar with view lifecycles in iOS but if by some chance there is not a method that fits your description and you really do need to have such an event, you could always use a call back combined with a property, ie
- (void)viewWillAppear:(BOOL)animated {
if (!self.specialMethodHasBeenCalled) { [self doSpecialMethod]; }
// other viewWillAppear stuff to do every time
}
- (void)specialMethod {
// do stuff
self.specialMethodHasBeenCalled = YES;
}
Might need to do some more stuff for thread safety, but this is just a hacky solution.

Override behaviour of pressing "space" in PDF Kit view for OSX

It seems like the standard behaviour of a pdfkit view when pressing space is to switch pages. We want to use the space button for other events (playing audio in our case).
Setting the space as shortcut in the menu does only work before the pdfkit view is interacted with. After that the behaviour is always set to switch pages.
We have also tried to intercept the space key-down-event using this code:
if (!keyDownEventMonitor) {
__weak IBBookViewController *weakSelf = self;
NSEvent * (^monitorHandler)(NSEvent *);
monitorHandler = ^NSEvent * (NSEvent * theEvent){
BOOL handleEvent = weakSelf.view.window != nil
&& theEvent.type == NSKeyDown
&& !theEvent.isARepeat;
if (handleEvent) {
switch ([theEvent keyCode]) {
case 49:
[self audioPlayPauseBtnHit:nil];
break;
default:
break;
}
}
return theEvent;
};
keyDownEventMonitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSKeyDownMask handler:monitorHandler];
}
but it does only extend the space functionlity so both the audio is played and the page is switched.
Any ideas?
You have other directions to go here.
Subclassing to override behavior.
( keeping in mind that the behavior may be inherited from a superclass or part of a class that is a private ivar in the header for the pdfview class. )
Looking at the responder chain and which object really gets the keyDown message first. Intercept and forward, possibly changing key view temporarily.
Creating a cover view that handles the keyDown events you care about first and forwards other events to the responder chain.

EXC_BAD_ACCESS when NSButton is pressed on NSWindowController

I am building a Mac application. I am adding a childWindowController to mainWindow. In my childWindowController, I have several buttons with their actions connected in IB. But when I press the NSButton, the application crashes and I get EXC_BAD_ACCESS message in the terminal. I also tried to perform setTarget:self, but that doesn't help at all.
Here's my code: applicationDidFinishLaunching
HomeWindowController *home_WindowController = [[[HomeWindowController alloc] initWithWindowNibName:#"HomeWindowController"] autorelease];<br/><br/>
[[self window] addChildWindow:home_WindowController.window
ordered:NSWindowAbove];
And in the HomeWindowController:
- (id)initWithWindowNibName:(NSString *)windowNibName
{
self = [super initWithWindowNibName:windowNibName];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
}
-(IBAction)action:(id)sender
{
NSLog(#"------------------ ");
}
What is wrong here? I am binding the NSButton to FileOwner and its action as well. Normally same as for iOS for IB. When I don't bind the IBAction, I don't get EXC_BAD_ACCESS.
It finally got resolved, I was releasing the childWindowController on appDelegate method after adding it on main window.But I dont understand why cant I release it …
Because you own the window controller, and the window controller owns its window. The window isn't keeping its controller alive; you are. And when you're not, it dies out from under anything that might want to talk to it, such as a button that has it as its target.
More generally, trying to shrug off your ownership responsibilities onto other objects—e.g., expecting a window to own its WC for you—is asking for memory-management bugs.
(My only exception to that is indirectly owning objects through collections: if I own, say, an array full of Things, I don't retain and release each Thing individually on its way in and out. Anything else, I expect to outsmart me.)
since it is a local instance of childWindowController on AppDelegate (just for adding it above)?
I don't understand what you meant by that.