My intent is to have 2 separate view controllers for a NSSplitView with two panes. In the left pane I want a table view, but it crashes. Here's the scenario:
I have a simple project with a MainMenu.xib, AppDelegate.h/m. In this project I add a LeftPaneViewController.h/.m/.xib.
In MainMenu.xib I add a NSSplitView to the default view.
In AppDelegate.h:
@property (weak) IBOutlet NSSplitView *splitView;
In AppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
LeftPaneViewController *lpvc = [[LeftPaneViewController alloc] initWithNibName:@"LeftPaneViewController" bundle:nil];
[self.splitView replaceSubview:[[self.splitView subviews] objectAtIndex:0] with:lpvc.view];
}
I connect the NSSplitView in MainMenu.xib to splitView in the AppDelegate.
When I run this works fine, but of course there is nothing to see yet.
In LeftPaneViewController.xib I add a NSTableView to the default custom view. I delete one of the columns (the bottom one in the list).
In LeftPaneViewController.h:
@interface LeftPaneViewController : NSViewController <NSTableViewDelegate, NSTableViewDataSource>
@property (weak) IBOutlet NSTableView *tableView;
@property (nonatomic, retain) NSMutableArray *tableDataSource;
In LeftPaneViewController.m:
#import "LeftPaneViewController.h"
@interface LeftPaneViewController ()
@end
@implementation LeftPaneViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Initialization code here.
}
return self;
}
- (void)awakeFromNib
{
self.tableDataSource = [[NSMutableArray alloc] initWithObjects:@"Row1", @"Row2", nil];
[self.tableView setNeedsDisplay];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
// This is a defensive move
// There are cases where the nib containing the NSTableView can be loaded before the data is populated
// by ensuring the count value is 0 and checking to see if the namesArray is not nil, the app
// is protecting itself agains that situation
NSInteger count=0;
if (self.tableDataSource)
count=[self.tableDataSource count];
return count;
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// Get an existing cell with the MyView identifier if it exists
NSTextField *result = [tableView makeViewWithIdentifier:@"LeftPaneCell" owner:self];
// There is no existing cell to reuse so create a new one
if (result == nil) {
// Create the new NSTextField with a frame of the {0,0} with the width of the table.
// Note that the height of the frame is not really relevant, because the row height will modify the height.
NSTextField *textField;
textField = [[NSTextField alloc] initWithFrame:NSMakeRect(2, 456, 125, 20)];
[textField setStringValue:@"My Label"];
[textField setBezeled:NO];
[textField setDrawsBackground:NO];
[textField setEditable:NO];
[textField setSelectable:NO];
[self.view addSubview:textField];
// The identifier of the NSTextField instance is set to MyView.
// This allows the cell to be reused.
result.identifier = @"LeftPaneCell";
}
// result is now guaranteed to be valid, either as a reused cell
// or as a new cell, so set the stringValue of the cell to the
// nameArray value at row
result.stringValue = [self.tableDataSource objectAtIndex:row];
// Return the result
return result;
}
@end
In the LeftPaneViewController.xib I hook the TableView dataSource and delegate to "File's Owner" (of LeftPaneViewController of course). and hook the tableView referencing outlet to the tableView in LeftPaneViewController.h. I checked these at least a thousand times ;-)
Then when I run I get the following crash log:
* thread #1: tid = 0x23427, 0x00007fff82893250 libobjc.A.dylib`objc_msgSend + 16, queue = 'com.apple.main-thread, stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #0: 0x00007fff82893250 libobjc.A.dylib`objc_msgSend + 16
frame #1: 0x00007fff88f8d972 AppKit`-[NSTableRowData _addViewToRowView:atColumn:row:] + 324
frame #2: 0x00007fff88f8d63f AppKit`-[NSTableRowData _addViewsToRowView:atRow:] + 151
frame #3: 0x00007fff88f8bbd5 AppKit`-[NSTableRowData _addRowViewForVisibleRow:withPriorView:] + 415
frame #4: 0x00007fff88f8b95a AppKit`-[NSTableRowData _addRowViewForVisibleRow:withPriorRowIndex:inDictionary:withRowAnimation:] + 272
frame #5: 0x00007fff88f8ac29 AppKit`-[NSTableRowData _unsafeUpdateVisibleRowEntries] + 740
frame #6: 0x00007fff88f8a7c1 AppKit`-[NSTableRowData updateVisibleRowViews] + 119
frame #7: 0x00007fff88f625a7 AppKit`-[NSTableView layout] + 165
frame #8: 0x00007fff88f15e65 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 112
frame #9: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #10: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #11: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #12: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #13: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #14: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #15: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #16: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #17: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #18: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #19: 0x00007fff8d11b4a6 CoreFoundation`__NSArrayEnumerate + 582
frame #20: 0x00007fff88f15fc6 AppKit`-[NSView _layoutSubtreeHeedingRecursionGuard:] + 465
frame #21: 0x00007fff88f15cfe AppKit`-[NSView layoutSubtreeIfNeeded] + 615
frame #22: 0x00007fff88f114ac AppKit`-[NSWindow(NSConstraintBasedLayout) layoutIfNeeded] + 201
frame #23: 0x00007fff88e0b0a8 AppKit`_handleWindowNeedsDisplayOrLayoutOrUpdateConstraints + 446
frame #24: 0x00007fff893d6901 AppKit`__83-[NSWindow _postWindowNeedsDisplayOrLayoutOrUpdateConstraintsUnlessPostingDisabled]_block_invoke_01208 + 46
frame #25: 0x00007fff8d0e9417 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #26: 0x00007fff8d0e9381 CoreFoundation`__CFRunLoopDoObservers + 369
frame #27: 0x00007fff8d0c47b8 CoreFoundation`__CFRunLoopRun + 728
frame #28: 0x00007fff8d0c40e2 CoreFoundation`CFRunLoopRunSpecific + 290
frame #29: 0x00007fff8c17aeb4 HIToolbox`RunCurrentEventLoopInMode + 209
frame #30: 0x00007fff8c17ab94 HIToolbox`ReceiveNextEventCommon + 166
frame #31: 0x00007fff8c17aae3 HIToolbox`BlockUntilNextEventMatchingListInMode + 62
frame #32: 0x00007fff88e08533 AppKit`_DPSNextEvent + 685
frame #33: 0x00007fff88e07df2 AppKit`-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 128
frame #34: 0x00007fff88dff1a3 AppKit`-[NSApplication run] + 517
frame #35: 0x00007fff88da3bd6 AppKit`NSApplicationMain + 869
frame #36: 0x0000000100001872 TableView_In_SplitView`main(argc=3, argv=0x00007fff5fbff7e8) + 34 at main.m:13
frame #37: 0x00007fff84d4d7e1 libdyld.dylib`start + 1
With breakpoints I determine that in LeftPaneViewController.m the numberOfRowsInTableView is called and returns 2, but viewForTableColumn is not called.
The TableView is Cell Based so I do expect the viewForTableColumn to be called.
If I simply add a NSTableView to the SplitView in MainMenu.xib and transfer the same code into AppDelegate.h/.m it works fine (and changing the hooks as well of course). That is, it does not crash and both numberOfRowsInTableView and viewForTableColumn are called.
So what am I not doing that is causing the crash?
Ok, found the answer. Since the LeftPaneViewController variable (lpvc) in AppDelegate.m was local it was being released after the replaceSubview call. So adding it as (nonatomic, retain) in AppDelegate.h fixed the crash. Turned on Zombies to find the answer ;-).