Unable to change delegate on NSSplitView - xcode

I have a storyboard that contains a main window (with a corresponding MainWindowController class), and a main view (an NSSplitViewController, with corresponding MainViewController class). For certain functionality I am attempting to set the delegate of the NSSplitView contained in the view to the MainWindowController class.
Without any IB linkage, the NSSplitView delegate is already set to the MainViewController at application launch. I am able to get a reference to the MainWindowController, but when I attempt to set the delegate to the window controller (which does implement NSSplitViewDelegate), I am getting the following:
*** Assertion failure in -[NSSplitView setDelegate:], /Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1404.34/AppKit.subproj/NSSplitView.m:600
This also happens if I attempt to set the delegate to nil.
Does anyone know why this might be so, whether there are restrictions on setting delegates, and if there is a way to use IB to set the delegate of an item in a view to another Controller?
Thanks.

I don't have a reference for this but I'm pretty sure the split view and the split view controller aren't meant to be separated. Fortunately, NSSplitViewController mirrors the delegate methods, giving you a chance to intervene. There should therefore be no reason to change the split view's delegate.

Related

Swift class to interact with Interface Builder via IBOutlet

Good evening all,
I'm slowly working through my first OS X app. I have been having a hard time getting my Swift class to interact with an NSPopUpButton. Just to make sure I was doing it right, I created a new project and successfully erased all entries and entered text into the NSPopUpButton via AppDelegate. However, as soon as I try to move the same functionality to my own class, I can't even get the IBOutlet connection across to the new class.
Is a particular subclass type required of a new class to work properly with interface builder?
Here is a screenshot of the class I have created, as well as AppDelegate where I am trying to call the function belonging to this class.
Finally, here is the IB element in question, should I be able to select my own class under the 'Custom Class' inspector?
I am an iOS developer, but I would imagine the same principles would apply to your situation.
A ViewController class and an interface created in interface builder are two seperate things. While it may appear that they are connected via an iboutlet, they are actually independent and one can be instantiated without the other.
Currently, you are only creating an instance of your ViewController class in your App Delegate - and that's all. The system has no idea that your xib even exists! The outlets will only be connected once your app connects your xib to your ViewController class.
How do we do this? It's actually quite simple. Instead of instantiating our view controller like this:
let viewcontroller = ViewController()
We would connect our view controller to our xib in the instantiation:
let viewcontroller = ViewController(nibName: "MainWindow", bundle: NSBundle().mainBundle)
The nibName is telling the system the file name of your xib, and the NSBundle().mainBundle is telling the system where to look for the xib.
This will all only work if your xib has been assigned a custom class, like you mentioned. In your xib in interface builder, select the entire view controller. Then, in the custom class inspector type in the name of your ViewController class (in your case: ViewController - it should autocomplete). This will make sure your outlets are connected.
And you should be set up!! Let me know if you have any more problems come up.
Good luck!
EDIT:
This replaces the first part of my answer, however the part about hooking things up in Storyboard remains true. Upon reconsidering, I've believe I've realized that we are only creating the view controller, and not adding it to our view. Despite this, I believe we can take a short cut solution by adding one method to your view controller subclass (the one we set in the Storyboard). Start typing in viewDidLoad, and it should autocomplete. Type in super.viewDidLoad() at the beginning of the method. After that, type self.listUpdate(). This should work if the classes are hooked up correctly in Storyboard. This means you can delete the variables you created in the App Delegate.
Reference: You might also find Apple's documentation on creating a view controller handy (it's in Objective C online, but can be easily converted to Swift - it's the concept that counts): NSViewController Class Reference

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.

How do I programmatically change a custom view object's member variables?

I appreciate you taking the time to read this and hopefully help me out!
I am writing my first program in Xcode 4, using Objective-C and Cocoa for the first time. I have been following the tutorials, but at this point I want to display custom graphics using a custom view, where the graphics change based on the values in my model object.
My controller class, GameController, based on NSObject, creates my model object during initialization, and successfully receives button action messages, updates the model object appropriately, and updates text strings on the window in return using its outlets. I accomplished this by creating an NSObject object in my xib file (which is called the default (?) name of MainMenu.xib, but it includes all the UI in my application), using the utilities pane to set its class to GameController, and then connecting its actions and outlets to the appropriate UI elements.
I have dragged a custom view into my window, and used the utilities pane to set its class to my custom view class, BoardView, which is based on NSView. In BoardView, I have overridden drawRect: to fill in the background of my view with a blue color, and then draw each of the graphics defined by my model class (GameStatus).
The problem is, I do not know how to access the data in GameStatus from my BoardView class, or have GameController update a member variable of BoardView. I have not instantiated BoardView anywhere besides in Interface Builder, by dropping a custom view on my window. I do not have a BoardView object in my xib file (the column or list on the left side in Interface Builder).
I tried to create an outlet to a BoardView object in my GameController class (which does have an object in my xib, as I mentioned above), and connecting that outlet to the BoardView custom view in my window, and Interface Builder seemed fine with that. However, when I run the program, the value of the BoardView outlet pointer in my GameController class is 0x0. It seems like the outlet is not being connected to a BoardView object. I don't know if I need to do something else in Interface Builder to make an actual object (I tried creating one in the list to the left, but then couldn't figure out a way to connect it to the actual custom view displayed on the window).
To add to the confusion, when I run my application, the BoardView area of the window will display the blue background, and in fact any other graphics which I define in the drawRect: function. However, without any way to talk to my model object, I can't change the graphics based on the state of the model. I'm not sure if the fact that the hard-coded graphics are displaying correctly means that there is an object there somewhere, or whether it is somehow drawing based on the general template of the class somehow. The latter doesn't really make sense to me, but if the former is true, I'm can't figure out how to talk to that object from other parts of my code.
I feel like I'm just missing some basic understanding of how Xcode / Interface Builder creates objects or something. I would really appreciate someone telling me exactly what I'm missing here in the connection between my MVC objects / classes.
Thank you in advance for any help you can give me!
EDIT 2011/09/06:
To download a copy of my project to take a look at it, go here:
http://talix.homeip.net/2011/rival/
That's my home server and I'm an amateur at this, so please leave a comment if it isn't working. Thanks! Also, if there is a better way to respond to comments other than editing my original post, please let me know; I'm also new to this website. ;-)
-Joe
It sounds like you're only instantiating one GameController and one BoardView, and that's happening in the nib.
This, then, is the heart of the problem:
I tried to create an outlet to a BoardView object in my GameController class (which does have an object in my xib, as I mentioned above), and connecting that outlet to the BoardView custom view in my window, and Interface Builder seemed fine with that. However, when I run the program, the value of the BoardView outlet pointer in my GameController class is 0x0. It seems like the outlet is not being connected to a BoardView object.
Where in your code are you needing a reference to your BoardView but getting nil? Is that in GameController's init method, or in some other method of GameController?
If it's inside init, this is what I'd expect. The code that loads nibs must initialize the objects before it connects the outlets.
(If it's outside of init, I'd suggest starting by disconnecting and reconnecting the outlet.)
View = BoardView
Controller = GameController
Model = GameStatus
In MVC, the controller usually brokers communication between the model and the view, so I suggest you handle it this way:
Add a gameController outlet to BoardView and connect it to your game controller.
In drawRect, have the BoardView get the game status from the game controller.
I also suggest you make GameController your application's delegate:
Delete RivalAppDelegate.[hm].
In your nib, delete the reference to Rival App Delegate, and connect the Game Controller reference to the File's Owner delegate outlet.
You've got two instance of BoardView in your nib. Hook up the one inside your window to the boardView outlet of GameController and delete the other.
In GameController.h, after #interface GameController : NSObject, add <NSApplicationDelegate>
In GameController.m, implement applicationDidFinishLoading and set up your application there. (You can even call setGameStatus there if you want to.)

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.

IBOutlet not getting set in Cocoa MVC project

I might not be using IBOutlet correctly, or some other subtlety with how NIB files work is causing me trouble - any help would be much appreciated (feel free to propose an alternate way to accomplish what I want).
I have a View object and a Controller object. Both are in the NIB. The Controller's init is also called when the NIB is loaded and the View is initialized in the 'awakeFromNib' callback.
I need a way to connect these two objects - specifically, enable the 'View' object to call functions on the Controller.
Based on documentation online, the way to get these connected is to define an IBOutlet in the View and connect it to the Controller in the Interface Builder. So i created an
IBOutlet Controller* _controller;
in the View interface and graphically connected it to the Controller object in Interface Builder by making a connection from the View to the Controller and assigning the _controller outlet to the Controller (the blue Generic Object box in Interface Builder).
At runtime though, _controller is always _nil. I have verified that the Controller's init was indeed called.
Is there something obvious I'm missing about this?
Any simpler way to connect these two? Since they're both created by the NIB I don't have a common object that has a pointer to both.
Try accessing the IBOutlet in viewDidLoad instad.
When awakeFromNib is called not all the IBOutlets are populated (even though the documentation seem to imply it).

Resources