Challenge:Make a Data Source


#1

An observation and a question.

First the observation, I tried using the variables named as shown on the diagram on page 115. However, the “newItemField” listed violates one of the new naming rules established with ARC, so I renamed it to “theNewItemField”. (No ivar can start with “new”.)

Now a question, I attempted to allocate and initialize my mutable array (“toDoItems”) in the AppDelegate ‘init’ method, but no matter how I tried to initialize the array, it would not be be recognized throughout the rest of the class. So I ended up doing it in (with a check first) in my ‘awakeFromNib’ (filling in a couple of the items) and also in the ‘createNewItem’ method. It then worked as expected. Anyone got an idea why it would not work in the class init method?

In addition to enabling editing (for the extra points :wink: ), I added a Delete Item button; however, I cannot figure out how to implement a delete using the keyboard keys. Maybe further in the book.


#2

Thanks for pointing out the problem with newItemField; we’ll fix that.

Regarding allocating your toDoItems array in -init, that should have worked. Can you clarify what you mean by “the array [is not] recognized throughout the rest of the class”? Do you mean that it is nil? Try putting a breakpoint in -init to verify that it is actually being called, and feel free to post your -init method here.

Great that you’re doing the challenges! About the delete button, we show how to set a key equivalent on page 139.

Adam


#3

Adam,

I see what is happening, but I just didn’t understand it at first. In this challenge, I declared the array was going to use as seen here:[code]#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDataSource> {
NSMutableArray *toDoItems;
}
@property (assign) IBOutlet NSWindow *window;
@property (weak) IBOutlet NSTableView *toDoTableView;
@property (weak) IBOutlet NSTextField *theNewItemField;
@property (weak) IBOutlet NSButton *deleteButton;

  • (IBAction)createNewItem:(id)sender;
  • (IBAction)deleteItem:(id)sender;

@end[/code]
The -init that does not work looks just like this:[code]-(id) init {

self = [super init];
if (self) {
    NSMutableArray *toDoItems = [[NSMutableArray alloc] init];
    [toDoItems addObject:@"Test Item"];  // warning that "Local declaration of 'toDoItems' hides instance variable" 
}
return self;

}
[/code]Indeed, the IDE issues a warning on the line indicated above. Changing the previous line as indicated in the following version works fine:[code]-(id) init {

self = [super init];
if (self) {
    toDoItems = [[NSMutableArray alloc] init];
    [toDoItems addObject:@"Test Item"];
}
return self;

}
[/code]

So my confusion came from past programming experience with objects that weren’t declared but were being started in the main programming function/method, something that doesn’t happen much in Cocoa but does when going through just Objective-C lessons. It is just one more lesson learned. :wink:


#4

Aha. :slight_smile: Glad you got it resolved! Look out for those compiler warnings.


#5

For extra points :wink: I’m having a problem reading the new item from the table view. I can double click on an item and type in a new value but I can’t ‘read’ the value to change the NSMutable array of toDoItem? The item goes back to the original value. Any clues?


#6

This gave me a little trouble until I remembered that the tableView is the thing you are changing and that it needs to do the change to the array. I did it using the following:

-(void)tableView:(NSTableView *)tableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { // When the user changes an item on the table view, // update the toDoItems array [toDoItems replaceObjectAtIndex:row withObject:object]; }

The -tableView:setObjectValue:forTableColumn:row: method is defined in NSTableView.h


#7

Great, thanks. That worked. I had a look in the documentation but didn’t recognize that as the method (‘command’ :wink: that I needed. This takes the object (value) in the table, in the selected cell, and puts it into the array (toDoItems). The array is then remapped to the tableView with reLoadData. Great.

Thanks.


#8

I need to move on to the next chapter :wink:

I added a delete button that only works when a row is selected. The add item button changes to edit item when a row is selected…

- (void)tableViewSelectionDidChange:(NSNotification *)notification { NSInteger rownum = [_toDoList selectedRow]; if (rownum == -1) // no row selected { [_deleteButton setEnabled:NO]; [_addButton setTitle:@"Add Item"]; } else // row selected { [_deleteButton setEnabled:YES]; [_addButton setTitle:@"Edit Item"]; } }


#9

I solved the challenge quite the same way as posted here. After that I took a look into the sample solution,
and was surprised and confused because it looked so different from anything what I had seen before in the book.
Now I got some, maybe silly, questions:

1.) In the previous examples we used a instance variable e.g. _voices - i did the same in this challenge. But in the solution i found this:

For me so far @property/@syntesize were just a way to generate setter and/or getter methods for instance variables (another book explained it like this).
Is this a way to even save the definition of a ivar or is there any difference between the two possibilities?

2.) Normally we had sent a message like this:

In the sample solution always “self” is used e.g.:

- (IBAction)add:(id)sender { NSString *text = [[self itemInputField] stringValue]; [[self itemInputField] setStringValue:@""]; [[self items] addObject:text]; [[self tableView] reloadData]; [[self tableView] scrollRowToVisible:[[self items] count] - 1]; }
Without “self” it works too, so I asked myself why it was done this way?

3.) In the synthesizer example we used dataSource without adding the protocol to the class, but in the sample code i found:

It works like a charm without the protocol - so it’s just bad programming style or a real mistake?

cu
Vertex


#10

I can handle the first 2 things you asked.

In 1, the current version of Xcode allows you to skip declaring an ivar within the braces in the declaration as has been done for a long time. It is more plain as to what is being done if you do so, but you can now just use the @property statement to do it. It will handle declaring the ivar as well as setting it up for producing the accessor methods in the implementation file by using @synthesize. You don’t have to have accessor methods, say for something that doesn’t need them, but then you have to ignore the warning.

In 2, I am afraid that Adam as well as those of us who did this challenge and posted earlier, are not practicing the best programming style all the time. In a statement such as “[[self items] addObject:obj]”, using “self” guarantees that “items” is within the instance you are currently working with. It goes along with using accessor methods properly with ivars. You can set an ivar directly, but you should use the accessor method, and in so doing you would generate code that went like this: “[self setMyVar:theVal]” as opposed to “myVar = theVal” – both would work (most of the time) the former works all the time.

I am not sure about your question 3. Is it possible the protocol is already adopted by a super?


#11

Thanks a lot for your explanation loumaag, it was a big help to give me a deeper understanding of the subject.

My considerations went in a similar direction, but I wasn’t sure about it. So it shouldn’t be a mistake to add the protocol,
because it makes the code more readable, I guess.

cu
Vertex


#12
  1. Lou is correct; to add a little more detail: skipping declaring the actual ivar and relying on @synthesize to do it for you is allowed if you’re using the “modern” Objective-C runtime. This includes 64-bit Mac and iOS. We don’t use this style in the book, but I believe I slipped and used it in the solution. Frankly it makes adding ivars quite a bit easier, as long as you’re using the modern runtime.

  2. Getting and setting ivars via property accessors is probably the safest way to go, but it can also be a lot more verbose and add unnecessary overhead. Safest because then anybody using KVO on your class’s properties will get the notifications they are expecting (and bindings will work). But sometimes it just makes more sense to reference the ivar directly. It may even be necessary, if for example your setter has some side effects that you want to avoid.

  3. In the case of this challenge, advertising that TodoController conforms to the protocol is unnecessary, but helpful. Unnecessary because Interface Builder doesn’t presently check outlet protocol conformance when making connections, helpful because if you use it, you can autocomplete the protocol methods in the implementation. For example, in a class that advertises its conformance to NSTableViewDataSource, try typing “- t” and the autocomplete will include matching protocol methods in the list. A convenient (and safe) alternative to copying the method declaration by hand.