NSRangeException while changing uitableview data - iphone

I am both new here and in IOS programming, so please bear with my newbee questions for a while :]
Here is the situation of my application before my question:
I have a view in which i added 3 different subviews. (1-categories, 2-words and 3-rules)
Within the categories subview I have a table of categories. (reads and writes from a categories plist and array)
According to the selected category I am changing the table ingredients for words table in the words subview. This table keeps the words the user enters for a selected category. (reads and writes from an nsmutabledictionary whose keys are the category names from the categories table and whose values are arrays of strings which were pre-entered by me to the plist)
Now an example to make things clear:
my categories array has: Size, Color
my words array for Size category has 3 strings in it like so: "tiny" , "big", "huge"
my words array for Color category has 2 strings in it like so: "red", "blue"
First when i select "Size" category, i go and fetch words within that category and when i move from categories subview to words subview i see everything inside the "Size" category correctly in my uitableview. (it prints "tiny", "big" and "huge")
But when i go back to my categories subview and select "Color" this time and go back to words subview i get a crash.
Seems like it calls tableView:cellForRowAtIndexPath: method, and i get an NSRangeException as below:
[NSMutableArray objectAtIndex:]: index 2 beyond bounds
Long story short, I want to know why the cellForRowAtIndexPath method is calling an index which is not there. Here is how inside my cellForRowAtIndexPath method looks like:
[self reinitializeWordsDictionary];
[self reinitializeWordsInSelectedCategoryArray];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [wordsInSelectedCategoryArray objectAtIndex:indexPath.row];
return cell;
Thanks in advance for the answers,
tiw

If anyone reading this has NSRangeExceptions not thrown by cellForRowAtIndexPath, check to ensure your UITableView isn't wrongly set to "static cells" content.

Sounds to me that the - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
method of the UITableView's data source is delivering wrong values. I would check that by setting a break point on this method and evaluate the returned values.

What are you giving back in
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
?

Related

Duplicate elements in UITableView per section

I have 6 sections in a UItableView, every section displays 2 cells, normally, I want it like this:
However, here is what I have:
Every indexPath.row is duplicated in every section.
Here is the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"avenir";
Avenir *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) {
cell =[[Avenir alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.section];
cell.equipeb.text=[arrayofClubB objectAtIndex:indexPath.section ];
return cell;
}
The elements are retrieved from two NSMutableArrays, one for the first left element in cell and the other for the right element cell.
What is wrong?
Thank you for helping.
You need to calculate the correct index from both the row and the column. Since you have two pairs of rows per section, you need to multiply section by two, and add row, which will be either zero or one. The end result should look like this:
NSUinteger pos = indexPath.section*2 + indexPath.row;
cell.equipea.text=[arrayofClubA objectAtIndex:pos];
cell.equipeb.text=[arrayofClubB objectAtIndex:pos];
You're always fetching the identical text for each section, since
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.section];
always returns the same value for each section (since indexPath.section contains the section's index). Perhaps you wanted to do to the following instead?
cell.equipea.text=[arrayofClubA objectAtIndex:indexPath.row];
Also, for these kind of uses, it might be a lot more straight forward to use the free Sensible TableView framework as it automatically handles displaying your arrays.

Populating UITableView row 0 static and the rest with NSArray

In one section of my app I have a UITableView which is working fine right now. I would like to set row 0 cell.textLabel.text to #"Some string". Once row 0 has been set I would then like to load the rest of the rows from an array. Currently on load my array populates the table view but I'm trying to set row 0 as a sticky. The closest example I can think of is a forum topic that is set to stay at the top. My array is constructed of returned data from a web service call.
It's been a while since I've messed with table views, and I'm having a blank on this one.
The table view is 1 section, and I get the rows by counting the elements in the array. Since I would like to create an additional cell (row 0) I would call [array count] + 1. I don't know if this approach is the best one which is why I'm reaching out to the community here.
Any insight or a shove in the right direction would be great at this point.
You're on the right track:
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [array count]+1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if ([indexPath row] == 0) {
// Code for first
[[cell textLabel] setText:#"First cell"];
} else {
[[cell textLabel] setText:[array objectAtIndex:[indexPath row]-1]];
}
return cell;
}
If you want the top of your table to be "sticky", why not consider using that string as a section header or title? In this case, the header stays visible at all times until the next section (e.g. if you had two sections, that is) is fully on the screen.
In any event, in one of my current projects I'm required to do roughly the same thing that you're doing and I have a static string being returned in row 0 (which scrolls off the top of screen when the table view scrolls down).
And in my UITableViewDataSource method, I always add one for the static cell to the number of objects in my array and in my "cellForRowAtIndexPath:" method, I increment the row by one when the indexPath.row is not zero. And if it is zero, I return my static string.
And dark_knight provides some nice sample code that illustrates what I was describing to you. So +1 to him/her.

Selecting multiple rows of a UITableView

This page
http://networkpx.blogspot.com/2009/07/multiple-row-selection-with-uitableview.html
mentions a way to implement table views that can allow multiple selections of rows.
At the time of this article, it seems that it was not a blessed way of doing this.
Now, apparently, Apple is allowing this kind of tableViews.
The article mentions this
NSArray* selectedRows = [tableView indexPathsForSelectedRows];
as a way of getting a list of all rows selected but this is not a legal functionality of the SDK.
The big question is: how do I get a list of all rows selected, so I can perform an action with them?
thanks
EDIT
To answer some questions... this is the code I am using to discover if a row is selected, but this is giving me zero entries.
NSMutableArray *selectedRows = [[NSMutableArray alloc] init];
for (int i=0; i<[list count]; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
UITableViewCell *aCell = (UITableViewCell*) [myTable cellForRowAtIndexPath:indexPath];
if (aCell.accessoryType == UITableViewCellAccessoryCheckmark) {
[selectedRows addObject:indexPath];
}
}
// selectedRows has always 0 entries... all cells give me their type as UITableViewCellAccessoryNone even those with checkmark
Apple will reject your app for for iOS 4 and below.
We found the following non-public API/s in your app:
indexPathsForSelectedRows
Create a NSMutableArray.
Whenever a table cell is selected insert the current indexpath in the array. whenever he deselects the cell remove it from the array. Final array will have everything you selected.
MultipleCheck in Table – UITableView Example with Demo
http://sugartin.info/2011/08/19/multiplecheck-in-table-uitableview/
Are you looking to have the user select multiple rows at the same time? If not you could create an array that holds each row that is selected as the user selects them.
Also for the check mark you could just subclass UITableViewCell.
How to subClass UITableViewCell and use it to not clear UILabel background color on UITabeViewCell selected?

What does "MyIdentifier" mean in Objective-c or iPhone programming

I am puzzled at the following line "static NSString *MyIdentifier = #"MyIdentifier";" in the method: cellForRowAtIndexPath
What does that line do?
Is it just creating a random pointer to an NSString object and assigning it the string?
Why is it being called MyIdentifier, I have seen this in many examples.
#import "AddToFavorites.h"
#implementation AddToFavorites
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
}
return self;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
reuseIdentifier:MyIdentifier] autorelease];
}
// Configure the cell
return cell;
}
#end
Here is another example, this one has a different string, CellIdentifier.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TimeZoneCell";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [self tableviewCellWithReuseIdentifier:CellIdentifier];
}
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
UITableViews can automatically reuse cells to save on memory. To take advantage of this, you must specify a "reuse identifier" which is used by the UITableView to be able to look up existing cells ("dequeueReusbaleCellWithIdentifier") with the same identifier as the one you will create if it can't find an existing cell.
The line creates a static variable (global in that it is shared by all code paths and only initialized once, but local in that you can only access it in this method) to hold the NSString for the identifier. My guess is that this is to ensure that the same pointer is used every time, as comparing pointers is quick and easy, while comparing the contents of strings can take a little bit longer.
For performance as mentioned but also to get help from the compiler in catching spelling errors. There is no checking of your identifier if you use a #""-string literal. The compiler will error out you if you misspell the static identifier. Also codesense will autocomplete the static identifier.
The identifier is a key or tag that allows you to have multiple separate collections of cells for different purposes.
This saves you time and RAM memory - let's find out how.
Let's suppose you had a contacts list application, with two types of contacts, businesses and friends.
If you wanted to display these differently, then you might design two types of cell - one with a picture (friend photo) and name in black font, and one with just the name of the company and no picture or icon.
When the user is using the application, it might need to display 3 friends and 4 companies with names starting with "A-M" at first, so it needs 3 friend cells and 4 company cells. You pass it these, and tag all the friend cells with the identifier "friend", and all the business ones with the identifier "business".
When later on the view changes and just wants names starting with "P-T", you might just have 7 businesses. Ideally you would re-use the cells you already created, so it requests 7 cells with identifier "business", and it turns out you already tagged 4 cells that you already created with "business", so it simply re-uses those. The remaining 3 you already created have the wrong tag, so it ignores those (or maybe deletes them?) and creates 3 new business type cells and gives them the tag "business".
By re-using cells in this way you save on Memory (only need as many cells can be displayed at once of each type), and Performance (no need to go to the effort of allocating and initialising new cells while scrolling up and down). You trade this off against the additional programmer effort of writing this selection code and giving things ids.
They could have automatically tagged cells based upon the objective-C type, but this wouldn't work if you programmatically created the contents of a cell rather than subclassing or using the Interface builder to lay out your cells. So they provide the identifier mechanism instead.
If you only have one type of cell in your table, just call it "Alice" and forget about it.

iPhone SDK: Inserting and updating a UITableView with a new row

I have a tableView that needs to be updated after information has been inserted from another view. If I perform a
[self.tableView reloadData];
The very next time I insert more information in another view and try to reload the table, all the currently visible rows are duplicated.
In other words, when I start up the app I have:
tableView:
Row 1
Row 2
Then I submit some information that will also show up in the table and suddenly I have:
tableView
Row 1
Row 2
Row 3 <- info I just added
Row 1
Row 2
My numberOfRowsInSection implementation looks like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ItemsController sharedItemsController].count;
}
My cellForRowAtIndexPath implementation looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ItemsController* controller = [ItemsController sharedItemsController];
NSMutableArray* recentItems = controller.listOfRecentItems;
CustomCell *cell = nil;
NSUInteger row = [indexPath row];
if( row < recentItems.count )
{
Items* item = [recentItems objectAtIndex:row];
if( recentCellData == nil )
recentCellData = [[NSMutableDictionary alloc] initWithCapacity:[indexPath length]];
if( [recentCellData count] > 0 )
cell = [recentCellData objectForKey:[NSString stringWithFormat:#"%d", row]];
if (cell == nil) {
UIViewController * view1 = [[UIViewController alloc] initWithNibName:#"CustomCell" bundle:nil];
cell = (CustomCell*)[view1 view];
[recentCellData setObject:cell forKey:[NSString stringWithFormat:#"%d",row]];
}
// do some other stuff here
}
// Set up the cell
return cell;
}
What's the best way to update the table and avoid duplicating the currently visible rows.
Thank in advance for all the help!
The error isn't in how you're reloading the table, it's in how you're providing data to it. Set a breakpoint in the data source methods and the method that adds new rows to see where you're going wrong.
You'll only end up with five items if tableView:numberOfRowsinSection: returns 5. Thats the simple answer to your question, but I see other problems here. I'm wondering why you have this test: row < recentItems.count. Is that array the same thing as [ItemsController sharedItemsController].count? You really need to be using the same array for both methods.
(Also, it's not a syntax error, but you shouldn't use the property syntax for things that aren't declared as properties. You should write [recentItems count] instead.)
I'm also confused by the code you use to set up the cell. Cells are meant to be reusable. That is, you create one cell, then reconfigure it every time in your implementation of tableView:cellForRowAtIndexPath:. Your code creates a cell for each item in your list. This is very memory-inefficient, and will likely crash your program due to insufficient memory on the iPhone if you keep lots of cells in memory like this.
The recommended approach is to call dequeueReusableCellWithIdentifier:. If that returns nil, then you set up a cell using the initWithFrame:reuseIdentifier: initializer. The table view is very smart, and will only ask you to redraw the cell when it needs you to.
Your recentCellData dictionary looks really shaky to me, too. What if you insert an item after the item with key #"2"? All the items with key #"3" onward will need to be shifted one element to the right to work the way you expect. That's a ton of bookkeeping that seems rather unnecessary to me. If you really needed something like this -- and to be clear, I don't think you do -- why wouldn't you use an NSMutableArray, which is much easier to use?
I added a bit more info above.