2
votes

I'm trying to understand how Subclassing works in Cocoa.

I've created a new Cocoa Application Project in XCode 5.1.

I drag a new Custom View onto the main window.

I create a new Objective-C class CustomViewClass and set it as a Subclass of NSView. This generates the following :

CustomViewClass.h
#import <Cocoa/Cocoa.h>

@interface CustomViewClass : NSView

@end
CustomViewClass.m
#import "CustomViewClass.h"

@implementation CustomViewClass

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code here.
        NSLog(@"Custom View initialised");
    }
    return self;
}

- (void)drawRect:(NSRect)dirtyRect
{
    [super drawRect:dirtyRect];

    // Drawing code here.
}

@end

Note that I added the NSLog(@"Custom View initialised"); line so I can track what is going on.

In interface Builder, I select the Custom View and within the Idenditiy Inspecter set it's custom Class to CustomView. Then I run the Application.

As expected I get a Custom View initialised message in the Console.

I do exactly the same with an NSTextField adding it to the window, creating a new class TextFieldClass and the NSTextField custom Class is to TextFieldClass. I also add a NSLog(@"Text Field initialised"); in the same place as above to track things.

However when I run the App, I only get the Custom View initialised message in the Console and not the NSLog(@"Text Field initialised");message.

So initially I think that NSTextField doesn't recieve the initWithFrame message when it is created. So I add an initialiser to TextFieldClass :

- (id)init {
    self = [super init];
    if (self) {
        // Initialization code here.
        NSLog(@"Text Field initialised");
    }
    return self;
}

However this still doesn't seem to get called.

I assumed therefore that NSTextField just wasn't being subclassed. However, when I add this method to TextFieldClass :

-(void)textDidChange:(NSNotification *)notification {
    NSLog(@"My text changed");
}

Run the app and lo and behold, every time I type in the text field I get the My text changed message in the Console.

So my question is, what is going on here? How does the NSTextField get initialized and how can you override it's initialiser?

Why does the Custom View seem to act differently to the NSTextField?

Source code here

2

2 Answers

2
votes

For your first question, NSTextFiled gets initialised via

- (id)initWithCoder:(NSCoder *)aDecoder

In this case, you have dragged a NSTextField from the palette and then changed the class to your custom text field class in the identity inspector. Hence the initWithCoder: will be called instead of initWithFrame:. The same is true for any object (other than Custom View) dragged from the palette

Instead, if you drag "Custom View" from the palette and change the class to your custom text field class, the initWithFrame: will be invoked.

The CustomViewClass you have created is the second case, hence initWithFrame: is invoked. The TextFieldClass is the first case, hence initWithCoder: is invoked.

1
votes

If you use the Interface Builder in XCode, you should use awakeFromNib to initialise your subclass.

- (void)awakeFromNib
{
    // Your init code here.
}

If you want to use your subclass programatically and using the interface builder, then use code like this:

- (id)initWithFrame:(NSRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initView];
    }
    return self;
}    

- (void)awakeFromNib
{
    [self initView];
}

- (void)initView
{
    // Your init code here
}