RaiseMan crashing XCode 10


#1

Quick question… what is supposed to be the dataSource here? I can make File’s Owner the dataSource and delegate, or I can drag an object into the dock and set its class to “Document” and then bind it as the dataSource and delegate. Neither work at the moment, but I’m wondering which is correct.

My problem is when I click the “Add Employee” button (this is using File’s Owner as dataSource), I instantiate my array, that works, I add a new employee, that works… but when I reload my data… I believe the “tableView: objectForTableViewColumn: row” method is crashing because the name it is getting from the table is " 0x60800002d7e0." I really don’t understand this method… it’s essentially a getter for that part of the table right? But if that part of the table hasn’t been set yet… of course it wouldn’t be able to get the name. But since this is all done automatically by invoking “reloadData,” I can’t tell that method to set the value first before trying to get it.

Let me make sure I have my bindings set correctly before I explore this more deeply. When I had an object dragged to the dock and it’s class set as “Document” and the tableView was bound to that… the array would instantiate, I could see its count increasing as I added employees, however the “numberOfRowsInTableView” method always had a 0 count for the SAME array. Total head scratcher there.

Help. :]


#2

It is the object that implements the DataSource protocol.

Take a look at this guide first: Table View Programming Guide for Mac.


#3

“It is the object that implements the DataSource protocol”.

Yes, I get that. In this case we are making Document be the DataSource Protocol and the delegate by including…

#import <Cocoa/Cocoa.h>

@interface Document : NSDocument <NSTableViewDelegate, NSTableViewDataSource>

@property (strong, nonatomic) NSMutableArray *employees;

@end

However, Document does not show up as an object in Interface Builder. In order to bind the tableView to something in IB, it needs to exist there. I can create an object like we did earlier in the book (speech synth) by dragging an object into the IB, setting its class as Document, and then setting the tableView’s dataSource to this new object by control-dragging. Or, I could set the dataSource (and delegate) to File’s Owner. Neither of these work at the moment, and both are creating different problems. Oddly, setting File’s owner as the dataSource allows numberOfRowsInTableView: to correctly get the array’s count, but then crashes shortly thereafter.

I have looked at the link you sent. My problem with it is that when they talk about tableViews, they do so in the context of VIEW based tableViews, and not CELL based tableViews…

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TableView/PopulatingView-TablesProgrammatically/PopulatingView-TablesProgrammatically.html#//apple_ref/doc/uid/10000026i-CH14-SW1.


#4

The problem seems to be right here:

-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row
{
    NSLog(@"1st TV func called");
    NSLog(@"row = %lu", row);
    NSLog(@"object at [0]: %@", _employees[0]);
    Person *person = [_employees objectAtIndex:row];
    NSLog (@"person: %@", person);
    NSString *identifier = [aTableColumn identifier];
    NSLog (@"identifier: %@", identifier);
    return [person valueForKey:identifier];
}

with the resulting output with my NSLogs all over the place to act as a trace…

RaiseManOldSchool[12833:18385862] Person class did init.**
RaiseManOldSchool[12833:18385862] _employee array count: 1**
RaiseManOldSchool[12833:18385862] num of rows in TV called: 1.**
RaiseManOldSchool[12833:18385862] 1st TV func called**
RaiseManOldSchool[12833:18385862] row = 0**
RaiseManOldSchool[12833:18385862] object at [0]: <Person: 0x60c0000272e0>**
RaiseManOldSchool[12833:18385862] person: <Person: 0x60c0000272e0>**
RaiseManOldSchool[12833:18385862] identifier: nameColumn**

I think when I try to return [person valueForKey:identifier] it’s crashing right there… am I passing nil?


#5

I don’t have the book, but a Person object is unlikely to have an attribute named nameColumn. Or does it actually have it? :slight_smile:

Maybe you wanted to write:

if ([identifier isEqualToString:@"nameColumn"]) {
    return [person name];
else {
    return @"undefined attribute";
}

#6

Person object is unlikely to have an attribute named nameColumn . Or does it actually have it?”

that part of the code is working right, it’s returning the identifier that I have named in IB as the name of the columns… nameColumn and raiseColumn.

My biggest question is this. What, if anything, do I bind the dataSource and delegate to? An XCode project that has a document class does not have an object in the dock that would seem to act as the obvious view controller. In previous projects, I would create a class that is a subclass of NSViewController. I would then drag a ViewController object into the dock, set this object as an instance of my controller class in the Identity Inspector, and put all view controller related methods etc. in there.

In this project, we have a Document class… and we appear to be using it as our dataSource and delegate. Fine. BUT, how does one set the dataSource and delegate. You cannot drag a ViewController into the dock and set it as an instance of Document… it won’t allow it. You can just drag in a regular Object, but this fails to call the tableView methods… I’m not sure why. You can also set the tableView to have “File’s Owner” be the dataSource and delegate, and this seems to work, up until it crashes somewhere in the unseen maze of keyValue coding. So if you, the almighty IBEX10, were to create a tableView with an add and remove button and NOT solely use cocoa bindings… how would you do it? Would you make the Document class the dataSource? When you control-clicked on the tableView and went to set the dataSource… you would drag it to… where?


#7

No, ideally, I would not make the document class the data source of the table view. I would use a dedicated view controller managing the table view and load an instance of it when a document instance is created and insert the view containing the table view into the content view of the document’s window.

But you can also make the document object the data source and delegate of the table view if the table view lives in the content view of document’s window.


#8

Interesting development. I’m not sure what I did, but I recreated a generic “object,” placed it into the IB Dock, set its class as “Document,” ctrl-dragged from tableView to this object and set my dataSource and delegate, and now it IS calling the tableView delegate functions… and it’s crashing just like when I have everything set to File’s Owner. So I now I need to unravel the mystery of why the app is crashing… it happens right as the tableView method goes to update the table. It’s being sent the correct identifier, it’s being sent the pointer to the Person object. One weird thing that is happening, I have an init in the Document.m file… and it’s being called twice when the app runs. Should it be doing this, or is this a clue I should pay attention to. Here is my init:

- (instancetype)init {
    self = [super init];
    if (self) {
        NSLog(@"document init.");
        [self.myTableView setDelegate:self];
    }
    return self;
}

and here is the console output when it runs…

2018-12-21 00:43:35.282779-0800 RaiseManOldSchool[874:46362] document init.
2018-12-21 00:43:35.289023-0800 RaiseManOldSchool[874:46362] document init.
2018-12-21 00:43:35.321709-0800 RaiseManOldSchool[874:46362] num of rows in TV called: 0.
2018-12-21 00:43:37.513856-0800 RaiseManOldSchool[874:46362] employees array created.
2018-12-21 00:43:37.513946-0800 RaiseManOldSchool[874:46362] Person class did init.
etc.

weird, right?

Another problem that bothers me… the person class has two elements to it, a NSString and a float. But we are only asking for the identifier of ONE column in the tableView method. How is the tableView supposed to know how to display the person… which is in fact an object with two pieces of information?

-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)row
{
    NSLog(@"1st TV func called");
    NSLog(@"row = %lu", row);
    NSLog(@"object at [0]: %@", self.employees[0]);
    Person *person = [self.employees objectAtIndex:row];
    NSLog (@"person: %@", person);
    NSString *identifier = [aTableColumn identifier];
    NSLog (@"identifier: %@", identifier);
    return [person valueForKey:identifier];
}

-(void)tableView:(NSTableView *)aTableView
    setObjectValue:(nullable id)anObject
    forTableColumn:(nullable NSTableColumn *)aTableColumn
             row:(NSInteger)rowIndex
{
    NSLog(@"2nd TV func called");
    NSString *identifier = [aTableColumn identifier];
    Person *person = [self.employees objectAtIndex:rowIndex];
    [person setValue:anObject forKey:identifier];
}

with output:
2018-12-21 00:43:37.514097-0800 RaiseManOldSchool[874:46362] num of rows in TV called: 1.
2018-12-21 00:43:37.518290-0800 RaiseManOldSchool[874:46362] 1st TV func called
2018-12-21 00:43:37.518339-0800 RaiseManOldSchool[874:46362] row = 0
2018-12-21 00:43:37.518446-0800 RaiseManOldSchool[874:46362] object at [0]: <Person: 0x604000026fe0>
2018-12-21 00:43:37.518510-0800 RaiseManOldSchool[874:46362] person: <Person: 0x604000026fe0>
2018-12-21 00:43:37.518548-0800 RaiseManOldSchool[874:46362] identifier: nameColumn


#9

You are working with a document based application. This means you don’t create the document objects, but you rely on the the system creating them for you when required.

See: Cocoa Document Architecture


#10

Gotcha. So if I don’t create an object so that I can ctrl-drag from the tableView to some thing so i can set the dataSource and delegate outlets… how does one set the dataSource and delegate outlets?

I finally broke down and just copied the solution from github. I set the two button outlets, and the one table outlet. I don’t know where to set the delegate and dataSource outlets though. if you don’t set them, nothing gets added to the table when you click “add.” If you set them to “File’s Owner,” the app crashes.

One of the big frustrations with learning Cocoa is the bindings aren’t set programmatically, so even though you have the code, there’s still things you need to do to make the app work… and those things aren’t ever spelled out anywhere.

Here is the code on github:

Do you need to set the tableView’s dataSource and delegate bindings, and if so, to where?


#11

OMG!!! I got it to work. Holy heck I’m so happy.

So, I was thinking about the person object, how it’s two pieces of information… and I was wondering, like I had asked earlier… how does the table “know” where to put one of those pieces of info, like the person’s name, vs. where to put the other… like the expected raise? It dawned on me that you would have to use the SAME NAMES as you had specified in person class. You can’t just arbitrarily name the columns “nameColumn” and “raiseColumn.” So I labeled the columns personName and expectedRaise and the F*****g thing works and I’m over the moon.