Wisebee
Wisebee

Reputation: 410

IBOutlet for NSOutlineView doesn't point a valid instance

I created a new Cocoa application project in Xcode then add a NSOutlineView and a NSTextView objects onto window. Those two objects were subclassed as MyOutlineView and MyTextView. After that I made two outlets for them and wrote code like below.

The problem, I found, is application has two different MyOutlineView instances in runtime. Working(valid) outline view instance is not equal to the myOutlineView outlet instance. What am I missing?

//
//  AppDelegate.h

#import <Cocoa/Cocoa.h>
#import "MyOutlineView.h"
#import "MyTextView.h"

@interface AppDelegate : NSObject <NSApplicationDelegate>

@property (assign) IBOutlet NSWindow *window;
@property (weak) IBOutlet MyOutlineView *myOutlineView;
@property (unsafe_unretained) IBOutlet MyTextView *myTextView;

@end

//
//  AppDelegate.m

#import "AppDelegate.h"

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)n
{
    NSLog(@"AppDelegate.myOutlineView(INVALID)::%@", _myOutlineView);
    NSLog(@"AppDelegate.myTextView::%@", _myTextView);
}

@end

//
//  MyOutlineView.h

#import <Cocoa/Cocoa.h>

@interface MyOutlineView : NSOutlineView <NSOutlineViewDataSource>;

@end

//
//  MyOutlineView.m

#import "MyOutlineView.h"

@implementation MyOutlineView

- (id)initWithCoder:(NSCoder *)aDecoder
{
    // This method is called first.
    self = [super initWithCoder:aDecoder];
    NSLog(@"MyOutlineView initWithCoder(INVALID)::%@", self);
    return self;
}

- (id)initWithFrame:(NSRect)frame
{
    // This method is also called but through a different instance with first one.
    self = [super initWithFrame:frame];
    NSLog(@"MyOutlineView initWithFrame(valid)::%@", self);
    return self;
}

- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    NSLog(@"MyOutlineView data source delegate(valid)::%@", self);
    return 0;
}

- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    return NO;
}

@end

//
//  MyTextView.h

#import <Cocoa/Cocoa.h>

@interface MyTextView : NSTextView

@end

//
//  MyTextView.m

#import "MyTextView.h"

@implementation MyTextView

- (id)initWithCoder:(NSCoder *)aDecoder
{
    // This method is called.
    self = [super initWithCoder:aDecoder];
    NSLog(@"MyTextView initWithCoder::%@", self);
    return self;
}

- (id)initWithFrame:(NSRect)frame
{
    // But this method is NOT called at all.
    self = [super initWithFrame:frame];
    NSLog(@"MyTextView initWithFrame::%@", self);
    return self;
}

@end

Output:

MyTextView initWithCoder::                 [MyTextView: 0x10013be80]
MyOutlineView initWithCoder(INVALID)::     [MyOutlineView: 0x10014bc90]
MyOutlineView initWithFrame(valid)::       [MyOutlineView: 0x1001604a0]
MyOutlineView data source delegate(valid)::[MyOutlineView: 0x1001604a0]
AppDelegate.myOutlineView(INVALID)::       [MyOutlineView: 0x10014bc90]
AppDelegate.myTextView::                   [MyTextView: 0x10013be80]

Because of this, I have to put "AppDelegate.myOutlineView = self;" into MyOutletView's implementation wherever it calls related methods of AppDelegate. It does not seem natural.

Upvotes: 1

Views: 260

Answers (3)

paulmelnikow
paulmelnikow

Reputation: 17218

Xcode doesn't seem to let you set an outline view's delegate or data source to itself.

So I'm guessing you're doing something like this:

enter image description here

Which is to say: instantiating a second copy of your custom outline view class.

Here's the output from this setup:

2012-09-26 14:11:34.511 testproj[30255:403] -[MyOutlineView initWithCoder:]
2012-09-26 14:11:34.531 testproj[30255:403] -[MyOutlineView initWithFrame:]

By removing the extra (highlighted) instance of My Outline View, the initWithFrame: line goes away.

To make the outline view its own delegate, do this instead:

- (void) awakeFromNib {
    self.delegate = self;
}

That said, the point of the Delegation pattern is avoiding the need to subclass. If you do need an outline view subclass, try overriding the NSOutlineView / NSTableView methods directly, instead of using the delegate protocol.

Upvotes: 2

rdelmar
rdelmar

Reputation: 104082

I can't reproduce your problem. I dropped all your code that you posted into a test app, and I only get one instantiation of each object. Neither of the initWithFrame methods are getting called when I try it. My output is:

2012-09-26 09:00:38.945 TextViewDoubleInstantiationProblem[451:303] MyTextView initWithCoder::<MyTextView: 0x100123990>
    Frame = {{0.00, 0.00}, {381.00, 182.00}}, Bounds = {{0.00, 0.00}, {381.00, 182.00}}
    Horizontally resizable: NO, Vertically resizable: YES
    MinSize = {381.00, 182.00}, MaxSize = {463.00, 10000000.00}
2012-09-26 09:00:38.953 TextViewDoubleInstantiationProblem[451:303] MyOutlineView initWithCoder(INVALID)::<MyOutlineView: 0x101a1cb90>
2012-09-26 09:00:39.005 TextViewDoubleInstantiationProblem[451:303] AppDelegate.myOutlineView(INVALID)::<MyOutlineView: 0x101a1cb90>
2012-09-26 09:00:39.005 TextViewDoubleInstantiationProblem[451:303] AppDelegate.myTextView::<MyTextView: 0x100123990>
    Frame = {{0.00, 0.00}, {381.00, 182.00}}, Bounds = {{0.00, 0.00}, {381.00, 182.00}}
    Horizontally resizable: NO, Vertically resizable: YES
    MinSize = {381.00, 182.00}, MaxSize = {463.00, 10000000.00}

Do you have any other code in your app that you're not showing?

Upvotes: 1

Phillip Mills
Phillip Mills

Reputation: 31016

The calls to initWithCoder: come from loading objects that are defined in a nib file. I assume that's what you want to have happen since you mention creating outlets. In that case, they call to initWithFrame: strikes me as more likely to be "invalid" than the coder one.

I'd set a breakpoint in initWithFrame: and trace where that call is coming from in order to identify the extra allocation.

Upvotes: 0

Related Questions