Making NSTableView cells editable (Hillegass chapter 6 challenge)

Discussion in 'Mac Programming' started by elorc, Apr 1, 2009.

  1. macrumors newbie

    Joined:
    Apr 1, 2009
    Messages:
    12
    #1
    Hey guys, newb here. :) I've been working my way through Aaron Hillegass' "Cocoa Programming for Mac OS X" book and I decided to try his challenge in Chapter 6: Make a Data Source. The program works fine, but I decided to try adding the bonus criteria he mentioned. It says to make the rows editable and provides a hint that NSMutableArray has a method called replaceObjectAtIndex:withObject:. This has me a bit stumped because I'm not sure how to incorporate this into the existing application. The NSTableView is set to editable, so if I double-click on a row I can type on it. Those changes aren't saved though since I don't have the proper code behind it.

    This is how my application looks right now (without the code relating to editing the rows):

    Code:
    //
    //  AppController.m
    //  DataSourceChallenge
    //
    
    #import "AppController.h"
    
    
    @implementation AppController
    
    - (id)init
    {
    	[super init];
    	
    	toDoList = [[NSMutableArray alloc] init];
    	
    	NSLog(@"init");
    	return self;
    }
    
    - (IBAction)addIt:(id)sender
    {
    	NSString *addedString = [textField stringValue];
    	
    	if ([addedString length] == 0)
    	{
    		NSLog(@"User tried to add a blank line.");
    		NSRunAlertPanel(@"Error", @"You cannot add a blank line to the to-do list.", @"OK", nil, nil);
    		return;
    	}
    	
    	[toDoList addObject:addedString];
    	NSLog(@"Added the string: %@", addedString);
    	
    	[textField setStringValue:@""];
    	[tableView reloadData];
    	NSLog(@"Ran reloadData.");
    }
    
    - (int)numberOfRowsInTableView:(NSTableView *)tv
    {
    	return [toDoList count];
    }
    
    - (id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
    {
    	NSString *ti = [toDoList objectAtIndex:row];
    	return ti;
    }
    	
    @end
    Code:
    //
    //  AppController.h
    //  DataSourceChallenge
    //
    
    #import <Cocoa/Cocoa.h>
    
    
    @interface AppController : NSObject {
    	IBOutlet NSTextField *textField;
    	IBOutlet NSButton *addButton;
    	IBOutlet NSTableView *tableView;
    	NSMutableArray *toDoList;
    }
    - (IBAction)addIt:(id)sender;
    @end
    Now I was expecting to have to add another method, something like - (IBAction)editIt:(id)sender but I'm not sure what to bind this to on the NSTableView. Is that even necessary or is there something else I need to be doing? I thought maybe something like this:

    Code:
    - (void)tableView:(NSTableView *)tv replaceObjectAtIndex:(int)indexRow withObject:(NSString *)tempNewString
    {
    	[toDoList replaceObjectAtIndex:indexRow withObject:tempNewString];
    	[tableView reloadData];
    	return;
    }
    But this has no effect. When I double-click a row and change the text, I hit enter and it goes back to what the value was originally. I know this is a bigtime newb question but I appreciate your help. :)
     
  2. macrumors G4

    Eraserhead

    Joined:
    Nov 3, 2005
    Messages:
    10,275
    Location:
    UK
    #2
    You don't add a method, you use an appropriate delegate for NSTableView to call the method in NSMutableArray ;).
     
  3. macrumors newbie

    Joined:
    Apr 1, 2009
    Messages:
    12
    #3
    Hmmm okay. How do I set up that delegate then? I already have made the delegate connection from the NSTableView to my AppController class in Interface Builder.

    I changed my init to:

    Code:
    - (id)init
    {
    	[super init];
    	
    	toDoList = [[NSMutableArray alloc] init];
    	[tableView setDelegate:self];
    	
    	NSLog(@"init");
    	return self;
    }
    Then I removed the replaceObjectAtIndex that I had before with:

    Code:
    - (void)tableView:(NSTableView *)tv editColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex withEvent:(NSEvent *)theEvent select:(BOOL)flag
    {
    	NSString *tempString = [tableView valueAtIndex:rowIndex];
    	[toDoList replaceObjectAtIndex:rowIndex withObject:tempString];
    	[tableView reloadData];
    	return;
    }
    It still doesn't work. Same behavior: I change the field, hit enter, it reverts back to the old value. I don't understand what to try next. What am I missing/screwing up?
     
  4. macrumors G4

    Eraserhead

    Joined:
    Nov 3, 2005
    Messages:
    10,275
    Location:
    UK
    #4
    [tableView setDelegate:self]; isn't needed in the init if the connection is done in Interface Builder

    That's not a delegate method, you want to use controlTextDidEndEditing: or something ;).
     
  5. macrumors newbie

    Joined:
    Apr 1, 2009
    Messages:
    12
    #5
    I'm starting to get really frustrated and annoyed with this. Here's what I'm using now:

    Code:
    - (void)controlTextDidEndEditing:(NSNotification *)aNotification
    {
    	int currentRow = [tableView selectedRow];
    	if (currentRow == -1)
    	{
    		NSRunAlertPanel(@"Error", @"No row is selected.", @"OK", nil, nil);
    		return;
    	}
    	
    	NSString *tempString = [tableView objectAtIndex:currentRow];
    
    	[toDoList replaceObjectAtIndex:currentRow withObject:tempString];
    	[tableView reloadData];
    	return;
    }
    When I edit a field, I now get the following in my debugging window:

    "2009-04-01 15:06:42.950 DataSourceChallenge[1416:10b] *** -[NSTableView objectAtIndex:]: unrecognized selector sent to instance 0x129690
    2009-04-01 15:06:42.950 DataSourceChallenge[1416:10b] *** -[NSTableView objectAtIndex:]: unrecognized selector sent to instance 0x129690"

    Also, a warning shows up on the "NSString *tempString ..." line that says: "Warning: 'NSTableView may not respond to '-objectAtIndex:' (Messages without a matching method signature will be assumed to return 'id' and accept '...' as arguments.')".

    I've tried objectAtIndex, objectValue, valueAtIndex, and I get the same exact result for everything. I have no clue how to get the new value from the table and the documentation I've read hasn't been helpful at all. I know the rest of the code works fine because if I replace the last three lines with:

    Code:
    	[toDoList replaceObjectAtIndex:currentRow withObject:@"Blah"];
    	[tableView reloadData];
    	return;
    the row updates to "Blah" once the focus is changed or the user presses the enter key. Why is this so complicated to do in Objective-C? What am I missing now?
     
  6. macrumors G4

    Eraserhead

    Joined:
    Nov 3, 2005
    Messages:
    10,275
    Location:
    UK
    #6
    In the line:
    Code:
    NSString *tempString = [tableView objectAtIndex:currentRow];
    tableView should be replaced with toDoList
     
  7. macrumors newbie

    Joined:
    Apr 1, 2009
    Messages:
    12
    #7
    Now using:

    Code:
    - (void)controlTextDidEndEditing:(NSNotification *)aNotification
    {
        int currentRow = [tableView selectedRow];
    	if (currentRow == -1)
    	{
    		NSRunAlertPanel(@"Error", @"No row is selected.", @"OK", nil, nil);
    		return;
    	}
    	
    	NSString *tempString = [toDoList objectAtIndex:currentRow];
    
    	[toDoList replaceObjectAtIndex:currentRow withObject:tempString];
    	[tableView reloadData];
    	return;
    }
    I get no warnings or errors, but it still just goes back to the old value. By setting tempString to the value of toDoList at the selected index, won't that just reassign the old value instead of the new one?
     
  8. macrumors 6502a

    Joined:
    Oct 29, 2006
    Messages:
    807
    Location:
    Virginia
    #8
    elorc, I think you're very much on the wrong track here and making this harder than it actually is. For what you want to do, you don't need to implement a TableView delegate at all.

    This is just a simple matter of implementing one extra function in the datasource, which is setObjectValue:forTableColumn:row. An NSTableView will call this method whenever the user edits a cell.

    So here is an extremely simple rough example of a one column table (without validation, or error checking, etc) where the user can edit the string in each cell. This assumes that you create an instance of this AppController and connect it as the dataSource for your NSTableView in Interface Builder.

    Of course, an even easier way to do this is using bindings. But without bindings, this is how it would work:

    Code:
    #import <Foundation/Foundation.h>
    
    // An instance of AppController is connected as the dataSource
    // for an NSTableView in a nib.
    
    @interface AppController : NSObject {
    	
    	NSMutableArray* ourArray;
    	
    }
    
    @end
    
    
    
    @implementation AppController
    
    
    #pragma mark NSTableView dataSource methods
    
    - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView {
    	
        return [ourArray count];
    }
    
    - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
    	
        return [ourArray objectAtIndex:rowIndex];
    
    }
    
    - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
    	
    	[ourArray replaceObjectAtIndex:rowIndex withObject:anObject];
    
    }
    
    
    #pragma mark init/dealloc
    
    - (id) init {
    
        if (self = [super init]) {
    		
            ourArray = [[NSMutableArray arrayWithObjects: @"one", @"two", @"three", nil] retain];
    		
        }
    	
        return self;
    	
    }
    
    
    - (void) dealloc {
    
    	[ourArray release];
    	
    	[super dealloc];
    }
    
    @end
    
    Hopefully that gets you back on track and then you can build from there. Good luck.
     
  9. macrumors G4

    Eraserhead

    Joined:
    Nov 3, 2005
    Messages:
    10,275
    Location:
    UK
    #9
    Sorry :eek: I haven't use the method described above.
     
  10. macrumors newbie

    Joined:
    Apr 1, 2009
    Messages:
    12
    #10
    Eddietr: That's perfect. I understand what's going on and feel like an idiot for not having figured it out sooner. Thank you. :)

    Eraserhead: No problem. I really appreciate the effort to help me straighten this thing out!

    One thing I was curious about though, and this isn't part of the challenge in the book but kind of an additional thing I thought of... what's the best way to get the text field to add its contents to the tableview if the user presses the enter key (instead of clicking on the command button)? I was looking at textDidChange: but I'm not sure if that's the approach. If it is, how do I determine what the change was (i.e., what the last key pressed was)?

    I know how to do this in C#/VB.NET but obviously they are entirely separate beasts. :)
     
  11. macrumors 6502a

    Joined:
    Oct 29, 2006
    Messages:
    807
    Location:
    Virginia
    #11
    The typical way (consistent with the Apple guidelines) is to keep the button, but make return '\r' the key equivalent for the button. You can also do this by making that button the default button for the window.
     
  12. macrumors newbie

    GregX999

    Joined:
    Apr 5, 2009
    Messages:
    17
    #12
    What a coincidence!! I've just now gotten to the same exercise in the same book and had the EXACT same problem - trying to use "ControlTextDidEndEditing" and getting the same exact errors.

    After struggling and searching the net for about an hour now, I found this thread and the above method worked like a charm.

    BUT... why isn't that method listed in the docs for NSTableView? :mad:

    Greg
     
  13. macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Messages:
    3,172
    Location:
    Dallas, TX
    #13
  14. macrumors 6502a

    Joined:
    Oct 29, 2006
    Messages:
    807
    Location:
    Virginia
    #14
    It's in the docs for an NSTableView dataSource.

    Oops, what I actually meant is that in this example this AppController is the tableview's datasource, not connected to the datasource. I should have made that more clear.
     
  15. macrumors newbie

    Joined:
    Jan 14, 2010
    Messages:
    1
    #15
    Likewise

    I was also at the same point in the book and having trouble with this one. I knew the correct way to do the editable bit but I was having some problems with my mutable array initialisation
     

Share This Page