views:

442

answers:

2

I've successfully created an NSCollectionView and added a label to the view prototype in IB, bound to a property of my represented object. I now want to programatically create an NSButton and NSTextField with the NSTextField bound to a property of my represented object. When the button is clicked I want to show and hide the NSTextField.

The problem I've come across is if I put my initialization code for my controls in the view's initWithCoder method, and the binding in the view's awakeFromNib, the binding doesn't get hooked up. If I put the initialization for my controls in the awakeFromNib, when the button is clicked, I don't have access to the controls in my view (they are null when printed out using NSLog).

From what I can tell it looks like the issue may be that the way NSCollectionView works is, it creates an instance of the view, then copies it for how every many objects are in the collection view. How do I get the the buttons to initialize and the binding to work with the copy of the prototype?

Below is my initialization code and my binding in the awakeFromNib for my subclassed view:

SubView.h

@interface SubView : NSView {
    NSButton *button;
    NSTextField *textField;
    IBOutlet NSCollectionViewItem *item; // Connected in IB to my NSCollectionViewItem
}

- (IBAction)buttonClicked:(id)sender;

@end

SubView.m

@implementation SubView

- (id)initWithCoder:(NSCoder *)decoder
{
    id view = [super initWithCoder:decoder];

    button = [[NSButton alloc] initWithFrame:NSMakeRect(50, 95, 100, 20)];
    [button setTitle:@"Begin Editing"];
    [button setTarget:self];
    [button setAction:@selector(buttonClicked:)];
    [self addSubview:button];

    textField = [[NSTextField alloc] initWithFrame:NSMakeRect(10, 10, 100, 75)];
    [self addSubview:textField];

    return(view);
}

- (void)awakeFromNib
{   
        // Bind the textField to the representedObject's name property
        [textField bind:@"value" 
    toObject:item 
        withKeyPath:@"representedObject.name" 
     options:nil];
}

- (IBAction)buttonClicked:(id)sender
{
    [button setTitle:@"End Editing"];
    [textField setHidden:YES];
}

@end
A: 

-awakeFromNib is not called for views copied from the prototype NSCollectionViewItem. Put your binding code in initWithCoder: and you should be fine.

kperryua
Right, when I try to put my binding code in initWithCoder: I get this error:An uncaught exception was raisedController cannot be nil*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Controller cannot be nil'I think it is because the "item" property (an IB Outlet to the CollectionViewItem) is not yet connected in initWithCoder. Is there another way to get to the representedObject?
Austin
Hmm.. good point. Probably your best workaround would be put the binding code in another method and then call it with -performSelector:withObject:afterDelay:0.0. It's not pretty, but it should work, since I don't think there's any alternative.
kperryua
I tried calling the binding code using the performSelector:withObject:afterDelay from my initWithCoder, but the problem is that the initWithCoder gets called, then awakeFromNib, then initWithCoder gets called for each collection view item. Could you modify your answer to show the binding code you think should work?
Austin
+3  A: 

This sounds similar to something I just did, so maybe it's what you need.

Subclass NSCollectionView and override the

- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object method.

In that method, retreive the item prototype, add your controls and bindings.

For example:

@implementation NSCollectionViewSubclass

- (NSCollectionViewItem *)newItemForRepresentedObject:(id)object {

    // Get a copy of the item prototype, set represented object
    NSSCollectionViewItem *newItem = [[self itemPrototype] copy];
    [newItem setRepresentedObject:object];

    // Get the new item's view so you can mess with it
    NSView *itemView = [newItem view];

    //
    // add your controls to the view here, bind, etc
    //

    return newItem;
}

@end

Hopefully this is somewhere close to where you need to be...

posty