views:

4922

answers:

8

I'm trying to subclass NSCell for use in a NSTableView. The cell I want to create is fairly complicated so it would be very useful if I could design it in Interface Builder and then load the NSCell from a nib.

Is this possible? How do I do it?

A: 

I do it like this:

/* example of a silly way to load a UITableViewCell from a standalone nib */

+ (CEntryTableViewCell *)cell
{
// TODO -- this is really silly.
NSArray *theObjects = [[NSBundle mainBundle] loadNibNamed:@"EntryTableViewCell" owner:self options:NULL];
for (id theObject in theObjects)
    if ([theObject isKindOfClass:self])
        return(theObject);
NSAssert(NO, @"Could not find object of class CEntryTableViewCell in nib");
return(NULL);
}

However it isn't very efficient and if you're loading lot of data it might hurt you. Of course you should be using a reuseIdentifier which should force this code to only run a handful of times per table.

schwa
A: 

I do it like this, but I'm almost sure it's worse than schwa's linear search for the matching object class.

static NSString *MyIdentifier = @"MyCell";

MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
 // TODO: try to avoid view controller
 UIViewController *vc = [[UIViewController alloc] initWithNibName:@"MyCell" bundle:nil];
 cell = (MyCell *)vc.view;
 [vc release];
}

This of course assumes that the file's owner is a UIViewController and you attach the view outlet to the cell.

I wish Apple would fill in the blanks here. Laying out UITableViewCells in xibs is handy sometimes, but to my knowledge they didn't provide any examples or obvious ways to use them.

amrox
+5  A: 

In IB, start an empty XIB. Now go to the pallete and drag in a UITableViewCell, double click to bring up and edit.

include only the custom UITableViewCell (no other UIViews or other top level controls) - make sure it's a real UITableViewCell in IB, or you cannot set a reuse identifier (as opposed to casting a UIView in IB as your custom UITableViewCell class). Then you can add lables or whatever you like within the cell, as well as setting the reuse identifier or set whatever disclosure indicator you might like.

To use, you provide code like this in the tableView:cellForRow:atIndexPath: method:

YourCustomCellClass *cell = (YourCustomCellClass *)[tableView dequeueReusableCellWithIdentifier:<IDYouSetInXIBFile>];
if ( cell == nil )
{
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:<YourXIBName> owner:self options:nil];
    id firstObject = [topLevelObjects objectAtIndex:0];
    if ( [ firstObject isKindOfClass:[UITableViewCell class]] )
     cell = firstObject; 
    else cell = [topLevelObjects objectAtIndex:1];
}

If you have any labels or other controls you want to reference in your code, wire them in IB to your custom cell class - NOT the file's owner, which you do not ever need to set using the above code (you can leave it as NSObject).

Edit: I note you are really looking for an NSCell answer, but the code approach to using IB should be identical in Cocoa with the Cocoa Touch code I used above as loadNibNamed is a standard Cocoa call.

Kendall Helmstetter Gelner
Somehow I did it wrong the first time, but your detailed instructions (the start) saved the day. Thanx!
JOM
+5  A: 

The question was about a subclass of NSCell; the other answers seem to be doing something else, likely taking advantage of UITableViewCell being a view.

NSCell is not a view. While laying a custom cell out in IB would be a useful thing to be able to do, I think the answer is basically "no, this is not possible". When you subclass NSCell, you're pretty much just doing your own drawing. There isn't support subcells, or parameterized auto layout (ala NSView's springs and struts), which is I suspect what you're looking for.

The only caveat is that you could design an NSCell subclass that did do layout of sub-elements and provided parameters for setting those subelements and all tweakable parameters. Then, you would need to write an IB plugin to make that cell and accompanying inspector available at design time in IB.

This, however, is probably harder than writing a little custom app that does more or less the same thing. Put an NSCell in a control in the middle of a window, and make yourself UI for tweaking the parameters you're interested in. Bindings can make this pretty straightforward for positioning stuff (i.e. bind an x value to a slider), though you will not get direct manipulation of the elements of course. When you're done, you could archive your cell and load the archive at runtime in your real app, or you could just log out the properties and set them in code in your app.

Ken
+1  A: 
Chris Hanson
+4  A: 

As Ken says, NSCells and NSViews are different, and you can only lay out NSView hierarchies in NIB, not NSCells (which don't have any explicit hierarchy).

On the other hand, there's nothing preventing you from having a hierarchy of NSViews and using that to draw your NSCell -- you could add them as a subview of your cell's parent view, tell them to display, and remove them from the window and nobody would be the wiser.

In this case, using a NIB would work. Although, it seems like a ton of hassle. Typically I've just replaced the object that takes NSCells with a custom one that takes my NSViews, but that means writing your own mouse-handling code, which is very touchy.

On the other hand, my approach lets you bind the views' values in NIB, so you don't have to do any extra work, which is cool.

-Wil

Wil Shipley
A: 

Add your UITableViewCell to you tableviewcontroller and declare an IBOutlet property:

@interface KuguTableViewController : UITableViewController { IBOutlet UITableViewCell *customTypeCell; }

@property (readonly) UITableViewCell *customTypeCell;

.... then in cellForRowAtIndexPath you can just use your cell and set it to be reused: static NSString *CellIdentifier = @"CustomCell" cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) cell = customTypeCell; cell.reuseIdentifier = CellIdentifier;

+1  A: 

Some answers in this thread have gone off topic because they're talking about Cocoa Touch, when the original question was about Cocoa - the 2 APIs are quite different in this regard and Cocoa Touch makes it easy because UITableViewCell is a view subclass. NSCell isn't, and that's the problem

For information, I had to do something very similar in NSOutlineView recently - which is basically the same, but a little harder if anything because you have to deal with disclosure / collapse of levels. If you're interested in the code, I posted about it here: http://www.stevestreeting.com/2010/08/08/cocoa-tip-using-custom-table-outline-cells-designed-in-ib/

HTH

Steve Streeting