Sometimes you may find your application has rather complex rows, and you want an NSViewController to manage each row in a “View Based” NSTableView. This is fairly easy to do, and I did it in my Cyr Wheel pattern editing application. My “Cell View” that I want for each row was designed in Xcode like:
A simple NSViewController subclass:
@interface CDPatternItemViewController : NSViewController // the thing we are editing @property(weak) CDPatternItem *patternItem; @end
The controller code is what is interesting. I use an NSWindowController subclass, and keep an array of the view controllers:
@interface CDPatternEditorWindowController () { @private NSMutableArray *_patternViewControllers;
I decided to lazily load the view controllers. On initialization/load of my window I fill the array with NULL:
- (void)_resetPatternViewControllers { _patternViewControllers = NSMutableArray.new; for (NSInteger i = 0; i < self._patternSequence.children.count; i++) { [_patternViewControllers addObject:[NSNull null]]; // placeholder } [_tableView reloadData]; }
_patternSequence is simply a wrapper around my model object, stored in my Core Data backed NSDocument; it has an array of things I want to show in the table
- (CDPatternSequence *)_patternSequence { return self.document.patternSequence; }
Then, the view based tableview method that requests the view simply creates and caches it:
- (CDPatternItemViewController *)_patternViewControllerAtIndex:(NSInteger)index { id currentObject = [_patternViewControllers objectAtIndex:index]; CDPatternItemViewController *result; if (currentObject == [NSNull null]) { CDPatternItem *item = [self._patternSequence.children objectAtIndex:index]; result = [CDPatternItemViewController new]; result.patternItem = item; [_patternViewControllers replaceObjectAtIndex:index withObject:result]; } else { result = (CDPatternItemViewController *)currentObject; } return result; } - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { CDPatternItemViewController *vc = [self _patternViewControllerAtIndex:row]; // don't set the identifier so it isn't reused... return vc.view; }
This should be fairly straightforward, and easy to read. But if you have questions let me know and I will clarify.
I don't cleanup the view controllers when the items are removed from the table. I could use the NSTableView delegate method tableView:didRemoveRowView:forRow: to drop my NSViewController cache at this time if I so wanted. However, my table is small..and I don't bother with this.
Hi Corbin. I have been using a similar approach for iOS table views. I have a project with several static table views, most of which have fairly complex interactions. I derive my controllers from NSObject since there really isn’t an equivalent of UIViewController for a UITableViewCell, although I guess I could have made the contentView the controller’s view.
I would like to ask what you think you gain from using an NSViewController over just a plain old NSObject?
Hi Joanna,
The NSViewController automatically loads the view in my case; it does the grunt-work nib loading. It also allows me to know when the view loaded and do stuff. I also have other reasons, which I’ll document later.
corbin
Hi Corbin
That makes sense. My “CellController” has a cell property and looks after loading it from the nib in much the same way as the view controller would. I think we are working along very similar lines.
Given the time, the only improvement I would like to add is to be able to incorporate a reuse identifier for larfer, dynamic tables.