NSTableView WILL NOT RELOAD - objective-c

Hey guys, so in my newest program I use an NSTableView to display words on the left, and thier definitions on the right. these words and definitions are load from a .plist file, and at application startup the table view loads these words and definitions and displays them just fine. My problem comes in when the user tries to add a word and definition using the text boxes and buttons, the word is actually added to the .plist, meaning the method is running fine, but the table view refuses to display the new line. only until after i quit the program and reopen it does the tableview display the new line. I tested to see if the table view was connected properly by sending it other messages such as selectedRow and dataSource, all came back with responces, and proper responces at that. Currently the class that is used as the dataSource and delegate is a subclass to my main class with all my varibles and dictionaries. (I am big on using as little classes as possible). Lastly I tried inserting noteNumberOfRowsChanged in before reloadData, but still nothing.
I have tested everything and it just seems that the reloadData method is not initiating anything. Like I said, my table view is being sent the message, the new info is actually being added to the dicitinoary adn array, the amount of rows is being updated by the count method, and what proves it even more is that when the program is restarted it displays everything just fine. below is the relevent code, where currentWordList and currentDefitionList are the Array and Dictionary suppying the data to the dataSource, and editLibraryCardList is the NSTableView I am trying to reload.
the dataSource class code:
#interface EditorDataTable : SAT_Vocab_MacController {
IBOutlet NSTableColumn *editLibraryWordColumn;
IBOutlet NSTableColumn *editLibraryDefinitionColumn;
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
#end
#implementation EditorDataTable
- (int)numberOfRowsInTableView:(NSTableView *)tableView {
return ([currentWordList count]);
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
if (tableColumn == editLibraryWordColumn) {
return [currentWordList objectAtIndex:row];
}
if (tableColumn == editLibraryDefinitionColumn) {
return [currentDefinitionList valueForKey:[[currentWordList objectAtIndex:row]lowercaseString]];
}
}
#end
method that adds the word to the list:
- (IBAction) editLibraryAddWordToLibrary: (id) sender {
if (self = [super init]) {
currentWordList = [[NSArray alloc] initWithContentsOfFile:userSATWordListPath];
currentDefinitionList = [[NSDictionary alloc] initWithContentsOfFile:userSATDefinitionListPath];
}
[currentWordList addObject:[[editLibraryNewCardWordInput stringValue]capitalizedString]];
[currentDefinitionList setObject:[editLibraryNewCardDefinitionInput stringValue] forKey:[[editLibraryNewCardWordInput stringValue]lowercaseString]];
aWordCounter = [currentWordList indexOfObject:[[editLibraryNewCardWordInput stringValue]capitalizedString]];
[aWordLabel setStringValue: [[NSString alloc] initWithFormat:#"%#", [currentWordList objectAtIndex: aWordCounter]]];
[aDefinitionLabel setStringValue: [[NSString alloc] initWithFormat:#""]];
[currentWordList writeToFile:userSATWordListPath atomically:YES];
[currentDefinitionList writeToFile:userSATDefinitionListPath atomically:YES];
[cardCountdownNumber setStringValue: [[NSString alloc] initWithFormat:#"%i", ([currentWordList count] - (1 + aWordCounter))]];
[editLibraryCardList noteNumberOfRowsChanged];
[editLibraryCardList reloadData];
}
Iv'e been stuck for days and any ideas will help! Thanks.
Zach

Have you tried debugging into your selectRowAtIndexPath method to make sure the reload occurs? (after you call [tableView reloadData] should be able to see this) Are you using UITableViewController?
If you wanted a callback after reload to know when its done, you could try:
[tableView reloadData];
[self performSelector:#selector(selectRowAtIndexPath:) withObject:indexPath afterDelay:0.0];

For those who are curious, i moved my code from the dataSource subclass to the main class, and it worked. i guess you cannot subclass the dataSource. Hope this helps!

Related

Populating NSTableView from NSMutableArray at button pressed

in an OSX app i'm currently developping to get familiar with obj-c, I want to populate a TableView. After some hours spent reading way too much blog posts, I can't understand how to add a row in my TableView.
Here is what I've done following this guide:
I have an NSMutableArray in my ViewController, this ViewController implement both interfaces NSTableViewDataSource and NSTableViewDelegate. And I implement both methodes as indicated in the guide. I also have a button and a tableView. When I click on the button, I fill my array with my own object, that's works great.
But what I want now, is when my array is populated, my tableview is too. I'm aware I need to bind those two in some way, but I have no idea how, can someone give some indication ?
Here is my code for my ViewController:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewEpisodes.delegate = self;
self.tableViewEpisodes.dataSource = self;
}
- (IBAction)btRefresh:(id)sender {
CalendarReader* reader = [[CalendarReader alloc]init];
self.episodes = [Episode getEpisodeFromEKEvents:[reader getLastMonthEventsForCalendarName:#"TV Shows"]];
[self.tableViewEpisodes reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(NSTableView *)tableView
{
return [self.episodes count];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColum row:(NSInteger)row {
// Retrieve to get the #"MyView" from the pool or,
// if no version is available in the pool, load the Interface Builder version
NSTableCellView *result = [tableView makeViewWithIdentifier:#"MyView" owner:self];
// Set the stringValue of the cell's text field to the nameArray value at row
result.textField.stringValue = [self.episodes objectAtIndex:row];
// Return the result
return result;
}
First, you are creating a cell view with an identifier which you have not declared, you need to do something like this (assuming you correctly adopted the UITableView protocol in your class):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *Ident = #"Ident";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Ident];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:Ident] autorelease];
}
[cell.textLabel setText: [yourArray objectAtIndex:indexPath.row];
return cell;
}
This is a delegate method for your NSTableView. It is called when the view is loaded so you need to provide a data source at runtime.
Second, I'm assuming you want one section with a number of rows equal to your data array. If this is so, you need to change the delegate method:
- (NSInteger)numberOfSectionsInTableView:(NSTableView *)tableView
to:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
Finally, keep in mind these above methods populate the ROWS, not the COLUMNS as you have it now. Once you populate your array, you need to invoke the method:
[yourTableView reloadData]
In order to refresh the table.
Hope this helps.
Thanks to #bryan-wheeler, I notice a message log when testing his code, and I found out, I was not implementing the correct method: here is my code for my ViewController now:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewEpisodes.delegate = self;
self.tableViewEpisodes.dataSource = self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return 1;
}
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
return [self.episodes objectAtIndex:rowIndex];
}
For beginner like me, you'll also have to implement the NSCopying protocol for the class stored in your data source array.
Right now, I only have one element in my TableView and it only show its memory address, but I'll update this answer as soon as I found out how to make it works for future beginner in my case.
EDIT: OK, it works !! My mistake was that: in the tableView:objectValueForTableColumn:row: method, I though I needed to return the Object representing the row given in parameter, but I had to return the one for the AND the cell given in parameter, now I found out, it's pretty obvious, but as a French, I didn't understand the method name correctly. Here is my code now:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableViewEpisodes.delegate = self;
self.tableViewEpisodes.dataSource = self;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [self.episodes count];
}
-(id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex{
if([[aTableColumn title] isEqual: #"Serie's name"]){
return [[self.episodes objectAtIndex:rowIndex] seriesName];
}else if([[aTableColumn title] isEqual: #"Season number"]){
return [NSString stringWithFormat:#"%ld", (long)[[self.episodes objectAtIndex:rowIndex] seasonNumber]];
}else if([[aTableColumn title] isEqual: #"Episode number"]){
return [NSString stringWithFormat:#"%ld", (long)[[self.episodes objectAtIndex:rowIndex]episodeNumber]];
}else{
return nil;
}
}
There is some optimisation to do for sure, feel free to propose. But it's doing the job.

*** Illegal NSTableView data source (<NSView: 0x102535290>). Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:

OK, this is driving me crazy, i have an Xcode OSX App that i have been working on. I made some changes recently and i have started getting the following error at compile time:
iModerate Desktop[72478:303] *** Illegal NSTableView data source (<NSView: 0x102535290>).
Must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
I cannot workout where this is coming from, i have implemented both these methods in my appDelegate:
- (NSView *)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
// The return value is typed as (id) because it will return a string in most cases.
id returnValue=nil;
// The column identifier string is the easiest way to identify a table column.
NSString *columnIdentifer = [tableColumn identifier];
// Get the name at the specified row in namesArray
NSString *theName = [[self.twitterClientsController arrangedObjects] objectAtIndex:row];
// Compare each column identifier and set the return value to
// the Person field value appropriate for the column.
if ([columnIdentifer isEqualToString:#"name"]) {
returnValue = theName;
}
return returnValue;
}
and this
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return[[self.twitterClientsController arrangedObjects] count];
}
And app delegate is set as NSTableViewDelegate
Now the extra weird/frustrating thing, is that i have no NSTableView in the xib, i did, but i have deleted them all. I have event opened the XIB in BBedit and searched for NSTableView and there is 100% not one in there!
So, help please! If i could work out what NSView: 0x102535290 is i could maybe track this down.
Help to save my sanity greatly appreciated!
Gareth
it is solved with me that way :
remove the datasource and the delegate connections from the interfaceBuilder.
make an outlet property for the your tableview in the .h file
in the .m file in applicationDidFinishLaunching method set the delegate and datasource manually for your tableview
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];

UITableView numberOfRowsInSection Will Not Load More than One Row

So I have a UITableView which should loop through a single NSMutableArray and use each of them as row labels. Currently the only way I can get this to run is with 0 or 1 rows, 2 or higher throws out an error saying the array index is off. I tried NSLog to output my array and can confirm it's reading all the Strings.
// table methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
// Set up the cell...
NSString *cellValue = [harvestRecipeList objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
return cell;
}
The array code is stored in the exact same file (MasterViewController.m) which I added below.
- (void)viewDidLoad
{
[super viewDidLoad];
harvestRecipeList = [[NSMutableArray alloc] init];
[harvestRecipeList addObject:#"Ice Cream"];
[harvestRecipeList addObject:#"Walnut Cake"];
[harvestRecipeList addObject:#"Cookies"];
[harvestRecipeList addObject:#"Salad"];
[harvestRecipeList addObject:#"Grilled Fish"];
//Set the title
self.navigationItem.title = #"BTN Recipes";
}
I would love any help on this it's been bugging me. I was using [harvestRecipeList count] but this throws the same array index error. And as I mentioned I can get the app to run perfectly fine with 0 or 1 rows - thanks in advance for any help!
EDIT: here is the error I'm getting in the output window after building:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
EDIT2: included below my property setup for harvestRecipeList
//MasterViewController.h
#interface MasterViewController : UITableViewController {
NSMutableArray *harvestRecipeList;
}
#property (nonatomic, retain) NSMutableArray *harvestRecipeList;
// and also my MasterViewController.m
#synthesize harvestRecipeList;
EDIT3
here's my source code zipped for this project. It's called treehouse, just a testing name for now but you can dl from my cloudapp here.
Updated Solution:
So I have checked your code and found the problem. Do the following:
Go into your storyboard and select the Master Table View (click where it says Static Content)
Click on the Attributes Inspector (looks like a downward arrow sort of) and change the content from Static Cells to Dynamic Prototypes
Click on the prototype cell and type Cell into Identifier field (this is what you are using as a cell ID
Also change the Accessory from None to Disclosure Indicator
In your tableView:numberOfRowsInSection return self.harvestRecipeList.count
In tableView:cellForRowAtIndexPath: you can remove the following two lines (as they are provided by the Storyboard):
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
Recreate your Push segue from your Master Cell to your Detail View Controller
It should all now work fine - and I've tested that it works. The basic problem was you had specified Static Cells rather than Dynamic Prototypes and the rest of the instructions are just mopping up. The NSRangeException was caused by only having a single cell so that was all that was displayed.
Hope this helps.
Previous Solution:
So, a few comments but, first, if you've updated your code can you post an update?
Your harvestRecipeList that you add objects to in ViewDidLoad does not appear to be the same harvestRecipeList that you synthesised - it will be local to the method so your instance variable will always be nil. You should always use self.harvestRecipeList - do this everywhere. This could easily explain your NSRangeException. Also see #4 below.
In your tableView:numberOfRowsInSection: you should return self.harvestRecipeList.count
If you are using the iOS5 SDK, you do not need to check if cell == nil as dequeueReusableCellWithIdentifier: is guaranteed to return non-nil cell. You do need to check if you are on the iOS4 SDK.
Change your #synthesize harvestRecipeList; to #synthesize harvestRecipeList = _harvestRecipeList; as this will assist with #1 and checking you are accessing the ivar.
Try #1 & #2 as a minimum and then post an update on the problems you are having. Hope this helps.
I examined the code you put in the ZIP file. I immediately noticed your using the iOS 5 Storyboard feature. I haven't got Xcode 4 nor the iOS 5 SDK, so I could not test that part of your application.
However, I went on and coded your Storyboard part by hand. I have tested your MasterViewController solely and found no errors. I added in the AppDelegate this method to replace the Storyboard automatical features and just show the view controller where you think the error is coming from.
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MasterViewController *myVC;
_window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] applicationFrame]];
myVC = [[MasterViewController alloc] initWithStyle:UITableViewStylePlain];
[_window setAutoresizesSubviews:YES];
[_window addSubview:myVC.view];
[_window makeKeyAndVisible];
return YES;
}
To prove your MasterViewController.m contains no error, I add this screenshot:
Conclusion: Your error is to be found somewhere else, probably in your Storyboard file. However I have never used that new functionality so I cannot help you with that. I suggest you review your Storyboard and put all attention to that file.
Okay, if your log messages display an array with five objects right before you try to query the second, and you application gives you an NSRangeException the bug is definitely not to be found in the code you show us.
Try to find it by placing various logs before and after any -[NSArray objectAtIndex:] and see which log doesn't come through after the call. There's your error.
Remember you can use
NSLog(#"%s", __PRETTY_FUNCTION__);
to show where you log message is coming from. Their also exists a line and file macro, but normally the function macro should help you enough.
Example:
NSLog(#"%s Before", __PRETTY_FUNCTION__);
[myArray objectAtIndex:anIndex];
NSLog(#"%s After", __PRETTY_FUNCTION__);
If your second log message doesn't come through, then you'll have found your error.
It is always best to use the setter and getter methods for your instance variables. It takes care of a lot of problems. My guess is that is your problem. So anywhere you want to use harvestRecipeList use self.harvestRecipeList
It would be useful to know what your property declaration is for harvestRecipeList

Passing variables (or similar) to newly loaded view

I am loading new views for a small iphone app, and was wondering how to pass details from one to another?
I am loading a tableview full of data from and xml file, then once clicked a new view is brought in via:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SubInfoViewController *subcontroller = [[SubInfoViewController alloc] initWithNibName:#"SubInfoView" bundle:nil];
[self presentModalViewController:subcontroller animated:YES];
[subcontroller release];
}
Next step would be to tell the newly loaded view which row had just been loaded?
Any idea, thoughts more than welcome, and please be gentle big newbie...
I typically create my own init method to do things like this. I think it would likely be better to pass in the corresponding "model" object represented by the tableView row, rather than the row number itself, like this:
In SubInfoViewController.h
#interface SubInfoViewController : UIViewController {
YourObject *yourObject;
}
#property (nonatomic, retain) YourObject *yourObject;
Then in SubInfoViewController.m:
- (SubInfoViewController*)initWithYourObject:(YourObject*)anObject {
if((self = [super initWithNibName#"SubInfoView" bundle:nil])) {
self.yourObject = anObject;
}
return self;
}
You'd create and present it this way:
// assuming you've got an array storing objects represented
// in the tableView called objectArray
SubInfoViewController *vc = [[SubInfoViewController alloc] initWithYourObject:[objectArray objectAtIndex:indexPath.row]];
[self presentModalViewController:vc animated:YES];
[vc release];
This could be adapted pretty easily to allow you to pass in any type of object or value (such as a row number if you still want to do that).
Add an instance variable to your view controller and declare a property corresponding to it, so after you alloc, init it, set it like subcontroller.foo = Blah Blah.

Retrieving cells in UITableView

I have a UITableView that once a cell is clicked, it pushes tableViewB, which contains customcells. These cells contain TextFields. Once the user does any updating, they click "Save", which then pops tableViewB and goes to the first UITableView. I would like to get all of the UITextField values from tableViewB when Save is clicked. What is the best way to go about doing that?
The problem is that I need to loop through all UITableView cells. I'm not sure how that is done or if it is even a good approach. Just looking for help on what is a good technique here.
in your tableViewB header, declare:
NSMutableArray *stringArray;
and in the implementation:
- (id) init { //whatever your tableViewB initializer looks like
if ([self = [super init]) {
//oldData is an NSArray containing the initial values for each text field in order
stringArray = [[NSMutableArray alloc] initWithArray:oldData];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
//Making the cell
[cell.textfield addTarget:self action:#selector(updateField:) forControlEvents:UIControlEventValueChanged];
....
//Setting up the cell
cell.textfield.tag = indexPath.row;
cell.textfield.text = [stringArray objectAtIndex:indexPath.row];
return cell;
}
- (void) updateField:(UITextField *)source {
NSString *text = source.text;
[stringArray replaceObjectAtIndex:source.tag withObject:text];
}
- (void) dealloc {
[stringArray release];
}
There are several ways you can choose to get your data back to the original table view, either by delegate, or by having the stringArray declared as a variable passed in to the tableViewB initializer rather than allocated there.
You should be aware that, in general, there are only about as many cell allocated as displayed on the screen. The cells that are not visible are actually not persistent but only get created when tableView:cellForRowAtIndexPath: is called. I suggest you create an array to cache the contents of all the text fields and which gets updated whenever a user leaves a text field (e.g. the textField:shouldEndEditing: method or something like that) is called.
If I understand your question - id each cell numerically and reference them in an array/climb the array to loop through them