Thursday 21 July 2011

NSViewControllers (Part 2)

Following on from the last post which was really the setting up of an NSViewController and we used it to switch views in and out of the window. And really we didn't need very much code to do it. This time we're going to subclass the NSViewController to set some properties in them.

When you create a subclass (using File->New->New File, or apple+N) and you tell Xcode that the parent class is NSViewController it also creates a nib file for that controller. As I was following on from the previous project, I already had the two nib files, so I deleted the newly created ones. Then in the TestView1.xib and TestView2.xib files I had to change the class of the File's Owner to our new NSViewController subclasses (which in my case were TestView1Controller and TestView2Controller) in the Identity Inspector pane on the right. Remember that we also created two NSViewControllers in the MainMenu nib file, so we need to tell Xcode that it should use our new subclasses for those as well. Again just set that in the Identity Inspector pane. Compiling and running showed that everything worked as it did before.

One of the properties that an NSViewController has is a title for the view. This is designed to be displayed somewhere in the UI. I'm going to put it in the title of the NSBox for want of anywhere else to put it. First we need to set the title for each of the controllers.

In the TestView1Controller.m file Xcode has already given us the initWithNibName:bundle: method and that seems like the best place to put it. But if we put code into there we discover that it never gets called. This is because when an object is created from the nib file a different init message is sent: initWithCoder:. We can either put the code in this method or into the awakeFromNib method.


- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    [self setTitle:@"Test view 2"];
    
    return self;
}


Obviously because this is an init method we need to call up to the superclass. Or if we put it into awakeWithNib then all we need is to set the title.



- (void)awakeFromNib
{
    [self setTitle:@"Test View 1"];
}


Finally all we need to do is to set the box title. Back in the action methods we just need to send the setTitle: message to the box like so.



- (IBAction)switchView1:(id)sender
{
    NSView *view = [testView1Controller view];
    
    [box setContentView:view];
    [box setTitle:[testView1Controller title]];
}


Now, if you compile&run the box displays the title that was set in the view controller.
(code for this project available to download http://www.mediafire.com/?h0810n5s808c0u5 Again with the mediafire, fingers crossed...)


We can also set the title with bindings. If we add an NSViewController iVar to the ViewControllerExampleAppDelegate called currentController, then in the bindings inspector pane in Interface Builder we can bind the NSBox's title property to the ViewControllerExampleAppDelegate with keypath being currentController.title. All we have to do to set the box's title is to set currentController to the NSViewController we want displayed. In the new example, I've refactored the duplicated code in the action methods into a method of its own, and now it all looks like this.

- (void)setController:(NSViewController *)controller
{
    NSView *view = [controller view];
    
    [box setContentView:view];
    [self setCurrentController:controller];
}

- (void)awakeFromNib
{
    [self setController:testView1Controller];
}

- (IBAction)switchView1:(id)sender
{
    [self setController:testView1Controller];
}

- (IBAction)switchView2:(id)sender
{
    [self setController:testView2Controller];
}

I had hoped that we could have set the box's contentView via bindings as well, but apparently not.
(code for example 3 available here: http://www.mediafire.com/?k797b2y2uuqjz6c )

There's still some more things to look at on NSViewControllers, I'm working out how to bind things inside the view (if thats possible, I think it is) and I want to look at how NSCollectionView uses view controllers.


Links to the other parts of the NSViewController articles
Part 1:- http://comelearncocoawithme.blogspot.com/2011/07/nsviewcontrollers.html
Part 3:- http://comelearncocoawithme.blogspot.com/2011/08/nsviewcontrollers-part-3.html
Part 4:- http://comelearncocoawithme.blogspot.com/2011/08/nscollectionview-redux.html

NSViewControllers (Part 1)

I seem to see a lot of people asking questions about NSViewControllers on Stack Overflow and I wondered about them as well for a quite some time. They are being used more frequently in newer API on Cocoa, and you can't program for iOS without them.

There are a few functions the NSViewController allows you to do:

  1. Create new views on the fly
  2. Provide a title for the view for displaying in a tab or popup menu
  3. Enable you to bind to a value in the view, before the view is actually created.
Lets look at each one of these separately.

Creating a view on the fly is used quite a lot, think of a complex application like iTunes. The left hand column has a list of different options and when you select one, it shows a different view in the right hand side of the window. Now I have no idea how iTunes does it, but this is the one of the UI designs that NSViewController is designed to help with.

NSViewController has a method - (NSView *)view which creates a view and returns it. The simplest use of NSViewController is like so

NSViewController *viewController = [[NSViewController alloc] initWithNibName:@"customView" bundle:nil];
NSView *view = [viewController view];


[parentView addSubview:view];


This simply creates a new NSViewController and tells it that the definition for the NSView is found in the customView.nib (or customView.xib) file in the main bundle. Then it creates a new NSView and puts that view into parentView. But really, using NSViewController in this way doesn't really add much over just creating the view directly. Where NSViewController is really useful is when you need to create the views on the fly. Lets assume, for the sake of argument, that iTunes does use NSViewController. When you're looking at the Music view you may not want the Movies view loaded into memory. NSViewController does this by not creating the view until you call -(NSView *)view or -(void)loadView. This allows you to create the view controller and hook things up to it, without having the view loaded and taking up memory until it's needed. Lets look at a more full example.


In our application delegate (called ViewControllerExample1AppDelegate in the example project), we have some iVars that get filled from the MainWindow.nib.




@interface ViewControllerExample1AppDelegate : NSObject <NSApplicationDelegate> {
@private
    NSWindow *window;
    NSBox *box;
    NSViewController *testView1Controller;
    NSViewController *testView2Controller;
}


@property (assign) IBOutlet NSWindow *window;
@property (assign) IBOutlet NSBox *box;
@property (assign) IBOutlet NSViewController *testView1Controller;
@property (assign) IBOutlet NSViewController *testView2Controller;


- (IBAction)switchView1:(id)sender;
- (IBAction)switchView2:(id)sender;



In the MainWindow.nib we've created a window with 2 NSButtons, and an empty NSBox. The two buttons are hooked up to the switchView1 and switchView2 methods and we'll look at what they do later on. The NSBox is connected to the box iVar. In the nibfile we've created two NSViewControllers and connected them to the appropriate iVars. The ultimate aim for this is that clicking on the first button will switch the view contained in the NSBox to the view from the first view controller, and the second button with switch it to the second view controller's view. A simple but common UI pattern.


We also need to create two nib files that the NSViewControllers use to generate their views. I just did this by creating a new file, selecting UI in the type column, and selecting View in the type. That gives a nib file that only has an NSView in it, and I just dragged whatever controls I wanted in the view. After these nib files have been created, we need to tell the NSViewControllers what nib file to do, and to do that you just need to set the Nib Name property on the NSViewController.


Whenever NSViewController is sent the loadView or the view message, it creates the view from the nib file and it sets the File's Owner object to the receiver, which is our NSViewController and this it uses the File's Owner object's view outlet to find out what NSView should be returned. This means that we have to connect it up in our nib files. The first part of this process is to set the type of the File's Owner object. By default it is of type NSObject, which isn't very useful for us, so in Interface Builder we change the class type property by selecting the File's Owner object, and in the inspector pane's Identity Inspector changing where it says the class is NSObject to NSViewController. Now if we try to ctrl-drag from the File's Owner placeholder object to the NSView, it will allow us to set that as the view outlet.


Finally, all we need to do is to make the buttons switch the views and that simply means creating the view and setting it in the box. We do this in the switchView1 and switchView2 actions, which look like this:




- (IBAction)switchView1:(id)sender
{
    NSView *view = [testView1Controller view];
    
    [box setContentView:view];
}


- (IBAction)switchView2:(id)sender
{
    NSView *view = [testView2Controller view];
    
    [box setContentView:view];
}

The controllers are simply asked for their views, and the NSBox is told to show it. The returned view is set to be autoreleased, so if you want to keep it around then you need to send it the retain message. In this instance the setContentView message sent to box does the retaining for us so when the content view is replaced the old view is released.

We can make the program slightly more complete by displaying a box when it initially starts by doing the same thing in the awakeFromNib message sent to the application delegate.



- (void)awakeFromNib
{
    NSView *view = [testView2Controller view];
    
    [box setContentView:view];
}


I'll look into the other things you can do with NSViewController in the next part as this seems to be getting quite long.


The project for this example can be downloaded from http://www.mediafire.com/?c3n3wob0sdppp9l (sorry its on Mediafire, hopefully it won't be taken down)
Apple's NSViewController documentation for Cocoa: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSViewController_Class/Introduction/Introduction.html


Links to the other parts of the NSViewController articles
Part 2:- http://comelearncocoawithme.blogspot.com/2011/07/nsviewcontrollers-part-2.html
Part 3:- http://comelearncocoawithme.blogspot.com/2011/08/nsviewcontrollers-part-3.html
Part 4:- http://comelearncocoawithme.blogspot.com/2011/08/nscollectionview-redux.html

Wednesday 13 July 2011

An Introduction

Everyone seems to start one of these blogs when they're learning Cocoa, so I thought I'd start one too. Partly it'll help me to remember what I've worked out, maybe it'll help someone else too.

I've been programming in C/C++ on Unix for about 13 years now and I thought I'd expand my skill set. As such its unlikely that this blog will cover much about learning objective C or C and I'll probably take knowledge of those things as assumed. Maybe not, I've not written anything yet so who knows.

Recently I've been playing around with Controllers, ArrayControllers, ObjectControllers and ViewControllers, so I think I'll start with something like that. I also have an interest in audio and MIDI and graphics programming, maybe something will come from that.

Some books that I can recommend that I've found useful are two from Big Nerd Ranch:
* iOS Programming: The Big Nerd Ranch Guide (2nd Edition) (Big Nerd Ranch Guides)
* Cocoa® Programming for Mac® OS X (3rd Edition)

Two very good tutorial style books that get the reader up to speed on beginning to program iOS and Cocoa, starting with explaining the basics of Objective C and using the developer tools Apple provide.

* Core Animation for Mac OS X and the iPhone: Creating Compelling Dynamic User Interfaces (Pragmatic Programmers)

This book on Core Animation is quite good as a reference and introduction to Core Animation concepts, but it isn't a tutorial book and assumes quite a high level of knowledge of Objective C and Cocoa technologies.

* Programming with Quartz: 2D and PDF Graphics in Mac OS X (The Morgan Kaufmann Series in Computer Graphics)

Programming with Quartz is a huge guide and reference to drawing with Quartz. I'd say its essential if you're planning on doing any custom drawing in your applications.