How to get reference to NSWindow from NSToolbarItem subclass - cocoa

I have a class CustomToolbarItem which inherits from NSToolbarItem. From within the CustomToolbarItem class itself, during initialisation, can I get a reference to the NSWindow instance to which it is added?
I have tried adding the following code to AwakeFromNib but the view is null.
var window = View.Window;

An NSToolbarItem has an associated view, and a view has an associated window, so something like:
myCustomToolbarItem.view.window
should get you what you want.

Related

Knowing when NSPopoverTouchBarItem will show its collapsed view

I have an NSPopoverTouchBarItem in my Touch Bar, created in Interface Builder.
The popover has a custom NSView inside it, and want to load data in it only when the view is activated, but I can't find any way to recognize when the contained NSTouchBar or NSView becomes visible.
According to docs, NSTouchBarDelegate does not have any delegate methods for the view appearing, either.
Which class should I subclass, or should I be monitoring viewWillDraw on my custom NSView and set up some hacky scheme?
The docs were not too clear, but subclassing NSPopoverTouchBarItem gives you -(void)showPopover:(id)sender and -(void)dismissPopover:(id)sender.
You can then define a delegate method to tell the parent class that this popover did show.
-(void)showPopover:(id)sender {
[super showPopover:sender];
[self.delegate touchPopoverDidShow];
}

Creating an outlet for a NSProgressIndicator inside a NSToolbar

I have this OSX storyboard-based application that starts with a NSSplitViewController like this:
This splitViewController has two viewControllers: master and detail.
Inside the window I have a NSToolbar. I dragged a NSProgressIndicator to that toolbar and Xcode embedded it inside a NSToolbarItem.
Now I need to create an outlet (not an action as explained on other stackoverflow questions) from the NSProgressIndicator to some class. First question is which one?
Xcode will not let I create an outlet. I have tried these options:
dragged from the ToolbarItem to masterController class file, detailController class file and to NSSplitViewController class.
dragged from the ToolbarItem to the delegate class.
dragged from the NSProgressIndicator to masterController class file, detailController class file and to NSSplitViewController class.
dragged from the NSProgressIndicator to the delegate class.
dragged from both the NSToolbarItem and from the NSProgressIndicator to the Window Controller First Responder.
In all cases dragging does not make a window appear to allow me to create the outlet.
For heavens sake, how do I create an outlet like that? To which class I drag it and how do I do that?
I'll assume your setup is more like this image:
Your Window scene is backed, by default, by an NSWindowController, to which you cannot add new outlets. You need to create a subclass of that, associate it with your Window and then you should be able to create outlets in that.
File > New File > Cocoa Class
Specify a name like "SpaceDogsWindowController", as a subclass of NSWindowController.
Then use select the window controller icon (blue circle) and select the Identity Inspector in Xcode. (CMD+ALT+3). Specify the name of your new class in the "Class" field.
Then try to hookup an outlet:
1) Show the Assistant Editor
2) Use the Jump Bar to ensure your custom class is visible (It's at the top of the assistant editor pane, it should say Automatic and you can tap that to then select your new class; If it says 'Manual', change it to Automatic).
3) If your are control-dragging and it's still not offering to make a connection, try dragging from the document outline (also shown in the screen shot).
You could then edit that progress indicator from other view controllers, which are descendants of that window's view hierarchy, using code like this:
if let windowController = self.view.window?.windowController() as? CustomWindowController {
windowController.progressIndicator.doubleValue = 0.4
}
or, in Objective-C, something like this:
CustomWindowController *myWindowControllerSubclass = self.view.window.windowController;
windowController.progressIndicator.doubleValue = 0.4;
Hope that helps.

Receive window notifications

I have an NSWindow set up in Interface Builder. I have set the class of File's Owner to my NSWindowController and linked the window property of the controller to my NSWindow.
My controller implements NSWindowDelegate.
Now, in my controller, I have added the following:
- (void)windowDidLoad
{
[super windowDidLoad];
[self.window setDelegate:self];
}
- (void)windowDidBecomeMain:(NSNotification *)notification
{
NSLog(#"Did become main.");
}
Still, -windowDidBecomeMain: isn't called. Does anyone know why this is?
EDIT:
Trying to show a window from AppDelegate on launch. The main nib (declared in Info.plist) contains a menu item only which is linked to the AppDelegate. In the application delegate, I show an icon on the status bar and when this icon is clicked, I display the menu from the main nib.
In the application delegate, I also want to display a window which should have a window controller assigned to take care of the logic.
I believe that when this works, I will receive my window notifications.
Now, the following code doesn't show the window and I can't figure out why.
DemoWindowController *dwc = [[DemoWindowController alloc] initWithWindowNibName:#"DemoWindowController"];
[dwc showWindow:self];
Note that self is the application delegate.
I suspect your problem is due to the fact that your window controller is not actually the object that is the nibs file owner.
When you change the class in interface builder you are telling it what outlets and actions are available (which is why you are able to drag to the window outlet) but you are still responsible for passing in this object yourself.
In the case of a non-document based application, you will have a main method which calls NSApplicationMain. What this does is basically look up and load the window nib that is specified in your info.plist file and pass the current NSApplication instance to this nib as the files owner (so even though you changed the class type to NSWindowController, the object being passed in is actually of type NSApplication).
The easiest way to fix your problem is to get rid of your window controller for now (as it isn't actually doing anything yet).
You should implement the -windowDidBecomeMain: method in your app delegate. Then Ctrl+drag from your window to your appDelegate to set it as the delegate of the window to get your notifications.
Update
To answer your question regarding the WindowController beware of the following two issues:
You are creating your window controller variable (dwc) in your applicationDidFinishLaunching: method. This is released the moment you leave the method taking your window with it. Create an instance variable to hold onto the window controller instead.
Ensure that your second window nib has its file owner set to NSWindowController (or your window controller type) and that its window outlet is connected to the window in the nib file.
Your window should now display.

Call IBAction in different class

My document based application has a window with a tableview. The tableview has a datasource which points to a class of type NSObject (called HopBill) which includes a NSMutableArray (aHopBill) and the needed tableview methods. So far so good.
For adding rows to the tableview I've added a sheet which is controlled from a NSWindowController (called HopBillSheetController). When pressing the OK button in the sheet. I actually need to do two IBActions (which is not possible): Add the row to the array of the tableview and close the sheet. I can connect the OK button in the sheet to the NSWindowController (to close the sheet) or connect it to the NSObject (to add the row to the array). But I want both :-)
Is it possible to call the IBAction in the NSWindowController from the NSObject? Or is there another way to do this?
I'm quite a beginner to Cocao and Objective-C, so please be gentle :-)
If your sheet is a nib/xib with an NSPanel, the call to close it is simply [panel close]; Assuming your window controller has a property for the panel, you can put the close code at the end of its row-adding IBAction. Or you could have the IBAction itself call another method if you prefer.
If your panel is running modal, you might need to stopModal too. (That's what's needed if everything stays frozen after the panel closes; otherwise never mind.)
Assuming hopBill, your data source, is a property of the window controller, any IBAction you write in the window controller also has access to hopBill; it can do everything you need.
So add a single IBAction to the window controller and connect the panel's OK button to it. That ought to work.
As for calling an IBAction from somewhere other than a control in a nib, yes, you can do that. Use a reference to the control as the sender arg, or nil if the IBAction doesn't use the sender arg.
You could also create your panel programmatically, or use NSAlert. But it sounds like your current setup is simpler -- and therefore better.
Take a look at this h file for an app controller: Apple's ClockControl example
The NSMutableArray *appointments property is the actual data source that will be used by the NSTableViewDataSource protocol methods. The IBAction "addAppointment" can access "appointments" directly: [self.appointments addObject:whatever atIndex:whatever];
The ClockControl example could be modified to use HopBill. You would import its declarations up top: #import "HopBill.h" And then instead of the "appointments" property, it would declare HopBill *hopBill; And "addApointment" would access HopBill's mutable array (aHopBill) like this: [self.hopBill.aHopBill addObject:whatever atIndex:whatever];
Why you can’t send messages to hopBill:
First, because although you declare it, you never initialize it. You have:
HopBill *hopBill;
[self.hopBill.aHopBill addObject: bHopAdditionAtInit];
It should be:
HopBill *hopBill = [[HopBill alloc] init];
[hopBill.aHopBill addObject: bHopAdditionAtInit]; // “self” won’t work here
Second, you’re declaring it inside an IBAction method, (doneHopBillSheet:), so it’s a local variable, accessible only within that method. If HopBill is holding your table’s data source cache, it should be a property of the controller which implements the NSTableViewDataSourceProtocol methods.
In your HopBill interface, you declare the aHopBill array to be a property, and you initialize it in HopBill’s init method (you should also release it in HopBill’s dealloc method). You need to do the same thing for the controller — it should have an instance of HopBill as a property, and that instance should be initialized in the controller’s init method.
If you want HopBillController to manage the tableview, its interface declaration should look like this:
#interface HopBillSheetController : NSWindowController <NSTableViewDelegate, NSTableViewDataSource> {
…
}
And, then, of course, you have to implement the relevant NSTableViewDelegate and NSTableViewDataSource methods.
Also, the controller must have an IBOutlet property for the tableview itself, and in the controller’s awakeFromNib method, it has to assign itself as delegate and datasource:
[self.tableview setDelegate:self];
[self.tableview setDataSource:self];
(The self-dot syntax assumes you’ve set up #property and #synthesize code for tableview.)
The IBAction method that adds items to your table must be in that controller class, or in a class that has a property which is an instance of the controller class. Then the IBAction method will have access to the aHopBill array and can add the new object to the array, after which it will call [tableView reloadData], which will in turn trigger the tableview protocol methods and update the table.
Now, that means that the xib containing the tableview has to have the controller as its file’s owner. Since you’re using NSDocument, I suspect that, instead, you would put the tableview outlet in the NSDocument subclass. And you would give that doc subclass a property which is an instance of the controller. The IBAction methods would also be in the doc subclass, and so they would have access to the controller and its HopBill property. Or maybe you would simply make the doc subclass the controller, rather than using the separate HopBillSheetController class. I’m not sure about the NSDocument stuff. But, remember, the IBAction method can itself call other methods, as long as it has access to instances of the classes in which those methods are declared.
Apple has an example using both the tableview delegate and datasource protocol methods. Go to this link and download the sample code: tableview example
It looks like a nice app. Good luck.

In NSWindowController subclass, [self document] returns null

I am using a custom subclass of NSDocument and a custom subclass of NSWindowController. The problem is that I cannot reference my custom document from my custom window controller.
In IB, in the TKDocument NIB I have File's Owner set to TKWindowController.
In my TKDocument subclass I have:
- (void) makeWindowControllers {
TKWindowController *controller = [[TKWindowController alloc] init];
[self addWindowController:controller];
}
Then in my TKWindowController subclass I overrode setDocument to make sure it was being called:
- (void) setDocument(NSDocument *) document {
NSLog(#"setDocument:%#", document);
[super setDocument:document];
}
and then (again in TKWindowController) my action which references the document itself:
- (IBAction) plotClicked:(id) sender {
TKDocument *doc = [self document];
NSLog(#"plotClicked %#", doc);
}
The NSLog in setDocument outputs the string returned by my [TKDocument description] override as I'd expect; I only put it there to see if it was being called. However, doc in plotClicked is null.
What might I have done wrong?
EDIT: I believe the problem is to do with NIBs. My Document has its own NIB with File's Owner set to the custom controller as mentioned above. The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.
So, how do I link the two? My question is really this: How do I obtain a handle to the current active document (or its windowcontroller) from MainMenu.xib?
Thanks
My Document has its own NIB with File's Owner set to the custom controller as mentioned above.
The File's Owner of a document nib should be the document. Consider that suspect #1.
The plotClicked action is fired from a menu item in MainMenu.xib. I believe it's hitting a new instance of the controller which isn't associated with the current, active document.
Did you put a window controller inside your main menu nib? If not, then that isn't the problem, since you must have wired up your plotClicked: menu item to the First Responder, and the window controller and its document will be in the responder chain.
If you did, then there's the solution: delete the window controller from the MainMenu nib and hook up your menu item to the First Responder, so that the action message goes down the responder chain, which will enable it to hit the document or window controller.
How do I obtain a handle to …?
The only Handles on the Mac come from Carbon; those Handles do not exist in Cocoa.
init is not a designated initializer of NSWindowController. You want one of these: – initWithWindow:, – initWithWindowNibName:, – initWithWindowNibName:owner:, or – initWithWindowNibPath:owner:.
Also, from the docs:
In your class’s initialization method,
be sure to invoke on super either one
of the initWithWindowNibName:...
initializers or the initWithWindow:
initializer. Which one depends on
whether the window object originates
in a nib file or is programmatically
created.

Resources