Register FAQ / Rules Forum Spy Search Today's Posts Mark Forums Read
Go Back   MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Reply
 
Thread Tools Search this Thread Display Modes
Old Apr 1, 2009, 11:39 AM   #1
elorc
macrumors newbie
 
Join Date: Apr 2009
Making NSTableView cells editable (Hillegass chapter 6 challenge)

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. :)
elorc is offline   0 Reply With Quote
Old Apr 1, 2009, 12:48 PM   #2
Eraserhead
macrumors G4
 
Eraserhead's Avatar
 
Join Date: Nov 2005
Location: UK
You don't add a method, you use an appropriate delegate for NSTableView to call the method in NSMutableArray .
__________________
If they have to tell you every day they are fair you can bet they arent, if they tell you they are balanced then you should know they are not - Don't Hurt me
Eraserhead is offline   0 Reply With Quote
Old Apr 1, 2009, 01:15 PM   #3
elorc
Thread Starter
macrumors newbie
 
Join Date: Apr 2009
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?
elorc is offline   0 Reply With Quote
Old Apr 1, 2009, 01:31 PM   #4
Eraserhead
macrumors G4
 
Eraserhead's Avatar
 
Join Date: Nov 2005
Location: UK
[tableView setDelegate:self]; isn't needed in the init if the connection is done in Interface Builder

Quote:
Originally Posted by elorc View Post
Code:
- (void)tableView:(NSTableView *)tv editColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex withEvent:(NSEvent *)theEvent select:(BOOL)flag
{
...
}
That's not a delegate method, you want to use controlTextDidEndEditing: or something .
__________________
If they have to tell you every day they are fair you can bet they arent, if they tell you they are balanced then you should know they are not - Don't Hurt me

Last edited by Eraserhead; Apr 1, 2009 at 01:37 PM.
Eraserhead is offline   0 Reply With Quote
Old Apr 1, 2009, 02:39 PM   #5
elorc
Thread Starter
macrumors newbie
 
Join Date: Apr 2009
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?
elorc is offline   0 Reply With Quote
Old Apr 1, 2009, 04:21 PM   #6
Eraserhead
macrumors G4
 
Eraserhead's Avatar
 
Join Date: Nov 2005
Location: UK
In the line:
Code:
NSString *tempString = [tableView objectAtIndex:currentRow];
tableView should be replaced with toDoList
__________________
If they have to tell you every day they are fair you can bet they arent, if they tell you they are balanced then you should know they are not - Don't Hurt me
Eraserhead is offline   0 Reply With Quote
Old Apr 1, 2009, 04:38 PM   #7
elorc
Thread Starter
macrumors newbie
 
Join Date: Apr 2009
Quote:
Originally Posted by Eraserhead View Post
In the line:
Code:
NSString *tempString = [tableView objectAtIndex:currentRow];
tableView should be replaced with toDoList
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?
elorc is offline   0 Reply With Quote
Old Apr 1, 2009, 05:40 PM   #8
eddietr
macrumors 6502a
 
Join Date: Oct 2006
Location: Virginia
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.

Last edited by eddietr; Apr 1, 2009 at 06:29 PM.
eddietr is offline   0 Reply With Quote
Old Apr 1, 2009, 06:23 PM   #9
Eraserhead
macrumors G4
 
Eraserhead's Avatar
 
Join Date: Nov 2005
Location: UK
Sorry I haven't use the method described above.
__________________
If they have to tell you every day they are fair you can bet they arent, if they tell you they are balanced then you should know they are not - Don't Hurt me
Eraserhead is offline   0 Reply With Quote
Old Apr 2, 2009, 08:52 AM   #10
elorc
Thread Starter
macrumors newbie
 
Join Date: Apr 2009
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.
elorc is offline   0 Reply With Quote
Old Apr 2, 2009, 10:17 AM   #11
eddietr
macrumors 6502a
 
Join Date: Oct 2006
Location: Virginia
Quote:
Originally Posted by elorc View Post
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)?
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.
eddietr is offline   0 Reply With Quote
Old Apr 8, 2009, 09:08 PM   #12
GregX999
macrumors newbie
 
Join Date: Apr 2009
Quote:
Originally Posted by eddietr View Post
Code:
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
	[ourArray replaceObjectAtIndex:rowIndex withObject:anObject];
}
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?

Greg
GregX999 is offline   0 Reply With Quote
Old Apr 8, 2009, 09:24 PM   #13
lee1210
macrumors 68040
 
lee1210's Avatar
 
Join Date: Jan 2005
Location: Dallas, TX
I know nothing about this stuff, but it looks like that method is in the protocol NSTableDataSource:
http://developer.apple.com/documenta...ableDataSource

The notes in eddietr's code says that an instance of the AppController is connected to the table's datasource, not the table itself.

-Lee
lee1210 is offline   0 Reply With Quote
Old Apr 8, 2009, 10:38 PM   #14
eddietr
macrumors 6502a
 
Join Date: Oct 2006
Location: Virginia
Quote:
Originally Posted by GregX999 View Post
BUT... why isn't that method listed in the docs for NSTableView?
It's in the docs for an NSTableView dataSource.

Quote:
Originally Posted by lee1210 View Post
The notes in eddietr's code says that an instance of the AppController is connected to the table's datasource, not the table itself.
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.
eddietr is offline   0 Reply With Quote
Old Jan 15, 2010, 01:39 AM   #15
pawzlion
macrumors newbie
 
Join Date: Jan 2010
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
pawzlion is offline   0 Reply With Quote

Reply
MacRumors Forums > Apple Systems and Services > Programming > Mac Programming

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Similar Threads
thread Thread Starter Forum Replies Last Post
All iPads: The iPad challenge! [CHALLENGE OVER] TechGod iPad 56 Apr 5, 2014 04:00 AM
OS-neutral: Chuck's Challenge 3D: Chip's Challenge Reborn N19h7m4r3 Mac and PC Games 1 Mar 2, 2014 02:18 PM
Excel cells that attribute a score if two cells are the same ellisfella Mac Applications and Mac App Store 0 Nov 21, 2013 05:48 AM
Help!!! is there a way to have the text not be editable? Afbar1114 iPhone/iPad Programming 4 Sep 10, 2012 05:02 PM
Making Editable PDF Textfields/Checkmarks minichrispy Mac Applications and Mac App Store 0 Jun 1, 2012 10:01 PM

Forum Jump

All times are GMT -5. The time now is 01:51 PM.

Mac Rumors | Mac | iPhone | iPhone Game Reviews | iPhone Apps

Mobile Version | Fixed | Fluid | Fluid HD
Copyright 2002-2013, MacRumors.com, LLC