How to correctly create a custom NSTableViewCell and load it from nib? - objective-c

I found some guidelines for creating something similar for UITableView, but I'm interested in how it's done for Mac OS X.
I have done the following:
I have an NSTableView with two columns. For the first row, the template TableCellView "Image & Text Table Cell View" from Object Library works just fine. For the second row, I would like to have something more complicated which requires a custom TableViewCell.
a) I created a xib file where I layed out the needed objects from Object Library.
b) I subclassed NSTableViewCell, connected my xib file to this class in Identity Inspector, and connected the IBOutlets for the stuff in my xib.
I then came back to my NSTableView, drag&dropped the "Text Table Cell View", got rid of the text field, and in the Identity Inspector set up the custom class to be my own subclass of NSTableViewCell.
in my
(NSView*) tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
method, I take the identifier
NSString* identifier = [tableColumn identifier];
and if it's equal to my second column identifier, I need to return an instance of my subclass.
But how do I do that? I couldn't find something similar to this:
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
I'm using a view-based NSTableView. I'm starting to suspect I am doing it all wrong and there's a better way. Any help greatly appreciated.

Related

View-based NSTableView From Xib

I have a NSTableView that is created programmatically. I have several options for customizing the cells in each column based on the column type and datasource (ie, it's very easy to have buttons or checkboxes based on the column type and what is in the datasource).
Now I need to be able to fully customize the cell, so I'm attempting to load an NSView from a xib and return it from the tables delegate's viewForTableColumn method. I haven't used IB much outside of iOS and I'm not very well versed as to how the various outlets and class types should be set, especially when the majority of the UI is created outside of IB. I've read many posts here and on other sites but the majority of examples either create all of the UI in IB or none of it.
Currently I have TestCell.xib which was created by selecting View from the New File dialog. I've also created an objective-c class called TestCell. In IB I've set the view's class to TestCell, and I've dragged outlets for a label control and a button to the TestCell class.
In the table's delegate class I have the following:
- (NSView*)tableView:(NSTableView*)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row {
NSView* view = [tableView makeViewWithIdentifier:customRowXibName owner:self];
if( view == nil ) {
NSArray* nibObjects = nil;
if( [[NSBundle mainBundle] loadNibNamed:customRowXibName owner:self topLevelObjects:&nibObjects] ) {
view = [nibObjects lastObject];
}
}
return view;
}
However, the table view doesn't show anything. I'm also getting the following errors for both controls in the view when loading the xib:
Failed to connect (button) outlet from (TableListViewDelegate) to (NSButton): missing setter or instance variable
I'm assuming that's because I'm setting owner to self when loading the xib.
My questions are:
In IB, what should the File's Owner placeholder be set to? Currently it's set to TestCell but I don't believe that is correct.
Is it ok to use "TestCell" as the identifier? Does this identifier need to be set in IB? Or do I need to call registerNib:forIdentifier on the table view?
When calling loadNibNamed, what should owner be set to?
I was able to get this to work by doing the following:
In IB, set the File's Owner to be the class that is loading the Xib (in this case, the NSTableViewDelegate).
In the delegate, create an outlet for your custom cell and hook it up in IB (I used the Connection Inspector with the File's Owner selected).
In tableView:viewForTableColumn:row call:[tableView makeViewWithIdentifier:#"Xib Name" owner:self]
If that returns nil, then call:[[NSBundle mainBundle] loadNibNamed:#"Xib Name" owner:self topLevelObjects:&nibObjects] with nibObjects being a nil NSArray*.
If loadNibNamed returns YES, then the outlet you created in the delegate should now point to the newly loaded view. Make sure to set the views identifier to #"Xib Name" so you can make use of cached views.

view-based NSTableView from external Nib, Subviews are not initialized

i try to get my head around view-based NSTableViews on OS X.
My Problem is that in the loaded view cell (NSView subclass) the subviews are not initialized when i try to assign the values in my delegate.
At the moment the correct count and the correct view is displayed, but i cannot access the subviews to assign the proper values.
What i have done so far:
Created the xib with the custom view cell in Interface Builder.
Created the custom class for the cell and assigned it in IB.
This work fine and i can see the properties in the Debugger. Correct class
and the properties are IBOulets that are wired to the correct fields.
I can see the call to:
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
In my NSViewController:awakeFromNib i do:
// Make my view controller the delegate
_applicationTableView.delegate = self;
// set the correct datasource
_applicationTableView.dataSource = [NWDataHolder sharedInstance];
// register the nib with the custom cell
[_applicationTableView registerNib:cellNib forIdentifier:#"ApplicationListViewCell"];
In my - (NSView *)tableView:viewForTableColumn:row: i do:
NWApplicationListViewCell *cell = [_applicationTableView makeViewWithIdentifier:#"ApplicationListViewCell" owner:self];
The correct class and cell is returned and i have access to the properties.
The problem is, that subview is not initialized, the property is nil and the new value could not be set:
Log(#"Application Name Label %#", cell.applicationNameLabel); => nil
I`ve seen some hints that the subviews are initialized lazyly, but i cannot find a way to make the eager initialize.
Any suggestions what i'm doing wrong ?
Thanks,
Oliver
Fixed the problem. But i do not understand why this really happens.
I wired the fields in IB to the File Owner, but not to my ApplicationViewCell.
After wiring the property to both everything works fine.

NSOutlineView + XIB

Just getting into NSOutlineViews and see them a useful control.
Is it possible to show a Xib as the root item??
Fritzables.
Yes, when you use a view based NSOutlineView. First register the nib you want to display for a cell using registerNib:forIdentifier: (windowDidLoad in a window controller would be a good place, awakeFromNib is also a possibility).
NSNib *cellNib = [[NSNib alloc] initWithNibNamed:#"MyCell" bundle:nil];
[self.outlineView registerNib:cellNib forIdentifier:#"MyIdentifier"];
Next in your outlineView:viewForTableColumn:item: you get a (possibly recycled) instance of your nib by using the earlier specified identifier:
- (NSView *)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
NSView *cellView = [outlineView makeViewWithIdentifier:#"MyIdentifier" owner:self];
// optional configuration here
return cellView;
}
I've got a blog post + mac app sample code that demonstrates this.
Yes. You just have to switch it to use view based cells.
In your delegate, implement outlineView:viewForTableColumn:item: to provide the correct XIB.
Yes it is Possible, NSOutlineView is another great visual control available in Mac OS X. Being descendant of the NSTableView It represents the hierarchical data. You can collapse and expand nodes, see parents and their children. In this article we will describe how to use NSOutlineView to show DASchema object which by its nature is a good example of the tree-like data.
For more info check Here

Why can't I define IBOutlets when using custom "prototype tableviewcells"

I have my own table view cell which is defined in my storyboard. I have also defined a custom UITableViewCell class for this special cell. So when I want to create an Outlet for my custom prototype cell I get an error that the Outlet cant be created.
Since this is not possible I have to do some ugly workarounds and use the tags in IB to reference the individual labels and buttons later on in my code.
I don't really see why this is not possible and I wonder if working with tags and [myCell viewWithTag:] is the best possible way to go here?
Because the outlet is a one-to-one connection between your controller and a specific item within the view. In the case of a prototype cell, it is simply a description of a cell that can have an arbitrary number of different items (i.e. rows in your table view). How would the controller know which item you are referring to (e.g. row 5 or 500)? That is why you are receiving the error message.
Lucas provided one method to refer to your connection via tags which works perfectly well.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentInIB"];
UIImageView *img = (UIImageView*) [cell.contentView viewWithTag:1];
//img.image = ...
//Access you prototype cell here to alter its style, example:
[[cell layer] setCornerRadius:10];
[cell setClipsToBounds:YES];
return cell;}
I assume you are using dynamic prototypes - in the attribute inspector of the tableview in the storyboard there is an option to select "static cells" or "dynamic prototypes". You can do what you are trying to do if you select "static cells" as there is only one cell in your tableview at run time for each cell in the storyboard. Using this approach you will only be able to use the cells you create in storyboard i.e. you will not be able to select the number of cells in your code.

Holding NSView instances in an array

Is it possible to store an NSView object in a mutable array? As I understand it, the view will be an object so the array should be able to hold it. Specifically, I want to hold several instances of a nib file, which I think would be loaded with an NSNib init, and then addObject to the array.
The idea is to display an NSView in each of the rows of a column in a TableView. I think it can be done because iTunes does something similar (with what I think is an NSImage) in displaying album artwork in a list view.
Still, any knowledge on the subject (or link to an example or tutorial) would be very appreciated.
TableViews usually don't hold an NSView for each item. They hold a number of NSViewTableCells (which are, system-wise, far more lightweight than NSViews), and they re-use these cells. They usually don't have many more cells than necessary to display the visible part of the TableView, AFAIK, and when the view is scrolled, cells that have become "invisible" are re-used.
So the best way to do this is to subclass the cell and to make the TableView display the contents using these. Using NSViews for every entry in a list of, say, my MP3 albums would be extremely expensive.
In Xcode goto File->New File and choose Objective-C class then in the drop down, choose to make it a subclass of UITableViewCell. Name it MyCell (for example)
Next in interface builder, create a new XIB and change its owner to your newly created class. You may also want to delete the default view and add a UITableViewCell. Set the owner's view outlet to this new tableview cell. Then add whatever you want to the UITableViewCell.
Then create a new UIViewController (which it may be helpful to create a new UITableViewController first and then change its type to UIViewController just so you get all the UITableViewDelegate methods added for you) and choose to add a XIB file for the UIViewController. Open the header file for the newly created UIViewController and add UITableViewDelegate protocol so the header may look like this:
#interface MyViewController: UIViewController <UITableViewDelegate, UITableViewDataSource>
On the view for the view controller (in interface builder) add a UITableView and then set its datasource and delegates to the owner. Make sure to import the MyCell.h header file. Then implement the tableViewMethods in particular for your UITableViewCell you would do something like this:
- (UITableViewCell *)tableView:(UITableView *) tableView
cellForRowAtIndexPath:(NSIndexPath *) indexPath
{
static NSString *MyCellIdentifier = #"MyCellIdentifier";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:MyCellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
//Here you would set properties of the cells items like labels, etc.
return cell;
}