Obj-C- Add new row to TableView when cell is tapped? - ios

I've got a tableview that allows users to add an item (a row) to an invoice (the tableview) when an existing row is tapped. That said, I can't seem to add an empty row because my code is trying to set the information in the cell with data from my specified array, but naturally, the count in the array is different from my data source (as I want the count to be +1).
E.g. I want to return 3 cells even if there are only 2 dictionaries in my array, and the third cell should be empty.
I want this because the third cell allows my user to fill out empty fields, while the fields in the previous two rows are populated with their already input data. Here's how I'm trying to return the extra row right now, but as mentioned above, it crashes my app due to the imbalance of dictionaries returned in my array.
Here's my code so far:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.allItems = [[NSMutableArray alloc] init];
self.itemDetails = [[NSMutableDictionary alloc] init];
}
//TableView delegates
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.allItems.count + 1;
}
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
static NSString *ClientTableIdentifier = #"InvoiceDetailsTableViewCell";
InvoiceDetailsTableViewCell *cell = (InvoiceDetailsTableViewCell *)[self.tableView dequeueReusableCellWithIdentifier:ClientTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"InvoiceDetailsTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
if (self.allItems.count == 0) {
} else {
cell.itemName.text = [self.allItems valueForKey:#"Item Name"][indexPath.row];
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
InvoiceDetailsTableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *itemTitle = cell.itemName.text;
NSString *itemDescrip = cell.itemDescrip.text;
NSString *itemCost = cell.itemCost.text;
NSString *itemTax = cell.itemTax.text;
[self.itemDetails setValue:itemTitle forKey:#"Item Name"];
[self.itemDetails setValue:itemDescrip forKey:#"Item Description"];
[self.itemDetails setValue:itemCost forKey:#"Item Cost"];
[self.itemDetails setValue:itemTax forKey:#"Item Tax Rate"];
[self.allItems addObject:self.itemDetails];
[self.tableView reloadData];
}

One significant problem is the line that says:
cell.itemName.text = [self.allItems valueForKey:#"Item Name"][indexPath.row];
Since your row count exceeds the number of items in your array, you will want to check the row number before accessing the array:
NSInteger row = indexPath.row;
if (row < self.allItems.count) {
cell.itemName.text = self.allItems[row][#"Item Name"]; // personally, I’d get row first, and then keyed value second
} else {
cell.itemName.text = #"";
}
You want to check to make sure that the current row is not the last (blank) row.

Related

iOS Datasource not being set as expected of the TableView

I have a TableView "cannedTv". Each cell contains another TableView "valuesTv". The structure of datasource for cannedTv is { NSString *name, NSArray *valuesArr }. valuesArr is set as datasource for valuesTv. cannedTv is an expandable tableview. Initially just name is displayed, when expanded the valuesTv tableView is displayed. This is my code for didSelectRowAtIndexPath and cellForRowAtIndexPath delegate methods of the tableviews.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"ROW Selected...TAG = %d", tableView.tag);
selectedValueIndex = -1;
if (tableView == self.cannedTV) {
// USer taps expanded row
if (selectedIndex == indexPath.row) {
selectedIndex = -1;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath ] withRowAnimation:UITableViewRowAnimationFade];
return;
}
// USer taps differnt row
if (selectedIndex != -1) {
NSIndexPath *prevPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevPath] withRowAnimation:UITableViewRowAnimationFade];
}
// User taps new row with none expanded
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
return;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table View TAG = %d ROW = %d", tableView.tag, indexPath.row);
if (tableView.tag == 10) { // cell.valuesTv
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesArray objectAtIndex:indexPath.row];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(valueTextViewTapped)];
[ccell.valueTextView addGestureRecognizer:gestureRecognizer];
//[ccell.valueTextView sizeToFit];
return ccell;
} else {
// cannedTv
static NSString *cellIdentifier = #"CannedCell";
cell = (CannedCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Set Datasource & Delegate for valuesTv tableview
cell.valuesTv.dataSource = self;
cell.valuesTv.delegate = self;
if (selectedIndex == indexPath.row) {
// Do expanded cell stuff
[cell.tapInsertLbl setFont:[UIFont fontWithName:#"OpenSans" size:8.0]];
cell.tapInsertLbl.hidden = FALSE;
[cell.contentView setBackgroundColor:[UIColor lightGrayColor]];
[cell.contentView.layer setBorderColor: (__bridge CGColorRef)([UIColor redColor])];
[cell.contentView.layer setBorderWidth:1.0f];
} else {
// Do closed cell stuff
cell.tapInsertLbl.hidden = TRUE;
cell.contentView.backgroundColor = [UIColor whiteColor];
}
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name;
// Set the valuesArray so it be used in populating the valuesTv
valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
ci = nil;
} else if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.tagLbl.text = [searchResults objectAtIndex:indexPath.row];
}
[cell.tagLbl sizeToFit];
return cell;
}
}
The problem I am facing is, every time the right datasource is shown shown in valuesTv. At this moment I have 2 arrays in valuesArray with 0th element having 1 object and 1st element having 3 objects (of NSString). Datasource of cannedTv also has 2 rows in it. Sometimes, one time both rows of canndTv shows correct datasource and then both shows same datasource. Sometimes, both rows shows the same datasource. I don't understand why valuesArray can't get the right datasource. While debugging also, I found that at times valuesArray has right datasource but the tableview is showing wrongly, at times the control doesn't go to // Populates valuesTv line and thus previously set valuesArray is only shown. I tried many ways, but can't get the results as expected. Also tried to set valuesArray in didSelectRowAtIndexPath after setting selectedIndex, but that also didn't help.
I am stuck on this since yesterday and can't get thru. Where am I going wrong due to which correct datasource is not shown/reflected on the tableviews. Can you please try to help me out. Any help is highly appreciated. Thanks a lot.
UPDATE :-
Created new datasource & delegate in CannedCell object itself - it contains the valuesTv.
#interface CannedCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property NSArray *valuesDataSource; // Contains the valuesArray contents
In my VC, removed datasource for cell.values.datasource & cell.valuesTv.delegate lines. In the cellForRowAtIndexPath method for top table, set the datasource like this :-
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name; //[tagsArray objectAtIndex:indexPath.row];
//valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
cell.valuesDataSource = ci.valuesArr; // SET DATASOURCE
// This is setting proper array always
ci = nil;
}
Commented the whole if (tableView.tag == 10) { in cellForRowAtIndexPath method.
And in CannedCell :-
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (valuesDataSource != nil)
return valuesDataSource.count;
else
return 0;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesDataSource objectAtIndex:indexPath.row];
ccell.valueTextView.tag = indexPath.row;
return ccell;
}
Yet the results are as earlier only. I mean even if array of 3 objects are set as valuedDataSource, the sub-table shows only single object array. What do the think now can be the reason for this ?
As suggested by Paulw11, I created other object to handle TableViewDelegate and datasource for the sub-table. Though that didn't make any difference in the results of the problem.
I set UITableViewDelegate & Datasource in my top table's Cell class. Passed the array to be the datasource of the table.
Importantly, after setting the datasource of sub-table on calling
[cell.valuesTv reloadData]
forced the sub-table to reload its data and that finally did the trick. Till then things weren't working.

iOS - Simple Table Sections with Array Data

I'm just starting with iOS/Xcode and have been Googling/Youtubing for an hour and can't find a matching tutorial. All I'm trying to do right now is display a table with a list of exercises (rows) that are grouped by bodypart (sections). The bodypart sections will never change, but the user will be able to add a custom exercise to a bodypart.
Now, I'm assuming that I need an array for the sections and also an array for exercises...creating those is simple enough. I'm running into a problem assigning exercises to specific sections. Here's an example of the faulty code that when rendered, displays both exercises under both sections...also there aren't any section names being generated in the table so I'm not sure where that comes into play either.
Here's a screenshot of the result (as a side note, not sure why my nav controller isn't rendering): http://i.imgur.com/icoJgEq.jpg
Create the individual items:
#property NSString *exerciseName;
#property NSString *exerciseCategoryName;
Create/Allocate the arrays:
#property NSMutableArray *exerciseCategories;
#property NSMutableArray *exercises;
self.exerciseCategories = [[NSMutableArray alloc]init];
self.exercises = [[NSMutableArray alloc]init];
Fill the arrays with some default data:
- (void)loadInitialData {
FNTExerciseCategories *category1 = [[FNTExerciseCategories alloc]init];
category1.exerciseCategoryName = #"Chest";
[self.exerciseCategories addObject:category1];
FNTExerciseCategories *category2 = [[FNTExerciseCategories alloc]init];
category2.exerciseCategoryName = #"Biceps";
[self.exerciseCategories addObject:category2];
FNTExercises *exercise1 = [[FNTExercises alloc]init];
exercise1.exerciseName = #"Bench Press";
[self.exercises addObject:exercise1];
FNTExercises *exercise2 = [[FNTExercises alloc]init];
exercise2.exerciseName = #"Barbell Curl";
[self.exercises addObject:exercise2];
}
Load the data:
[self loadInitialData];
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return [self.exerciseCategories count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.exercises count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ExercisePrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MFTExercises *exercise = [self.exercises objectAtIndex:indexPath.row];
cell.textLabel.text = exercise.exerciseName;
return cell;
}
Thank you very much to anybody that can chime in!
Actually in tableView:numberOfRowsInSection: you are returning the count of the entire exercises array. So with your sample data you would have two rows per section. Try making an array of exercises for every section and then code something like the following:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (section == 0) {
return [self.chestExercises count];
}
else if (section == 1) {
return [self.bicepsExercises count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ExercisePrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
MFTExercises *exercise;
if (indexPath.section == 0) {
exercise = [self.chestExercises objectAtIndex:indexPath.row];
}
else if (indexPath.section == 1) {
exercise = [self.bicepsExercises objectAtIndex:indexPath.row];
}
cell.textLabel.text = exercise.exerciseName;
return cell;
}
In this case the chestExercises array would only contain the "Bench Press"-exercise and the bicepsExercises would only contain the "Barbell Curl"-exercise. So you would get one row per section.
For achieving that the sections have titles you would need to implement the method
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section {
return [self.exerciseCategories objectAtIndex:section];
}
which gives the sections the title according to the names stored in the array.
A more sophisticated way to build your datasource would be to create a NSDictionary with the section names as the keys (bodyparts) and the values being arrays containing the exercises for the bodypart. For instance if your categories are merely strings you could build such a dictionary with your sample data (for the purpose of demonstration I added another exercise):
FNTExerciseCategories *category1 = [[FNTExerciseCategories alloc]init];
category1.exerciseCategoryName = #"Chest";
[self.exerciseCategories addObject:category1];
FNTExerciseCategories *category2 = [[FNTExerciseCategories alloc]init];
category2.exerciseCategoryName = #"Biceps";
[self.exerciseCategories addObject:category2];
FNTExercises *exercise1 = [[FNTExercises alloc]init];
exercise1.exerciseName = #"Bench Press";
FNTExercises *exercise2 = [[FNTExercises alloc]init];
exercise2.exerciseName = #"Barbell Curl";
FNTExercises *exercise3 = [[FNTExercises alloc]init];
exercise3.exerciseName = #"Another Exercise";
// the instance variable self.exercises is a NSMutableDictionary now of course
self.exercises = [[NSMutableDictionary alloc] init];
exercises[category1.exerciseCategoryName] = #[exercise1];
exercises[category2.exerciseCategoryName] = #[exercise2, exercise3];
The advantage here is that you now have one dictionary containing all arrays that contains all your data. So as you're adding more data you don't have to change your implementation of the tableView datasource. BTW I am using Modern Objective-C syntax for the dictionary and arrays.
Having created a dictionary like that you could then simply implement your table view data source like so:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
// This gives the name of the category at the current section.
// It is then used as a key for the dictionary.
NSString *currentCategory = [[self.exerciseCategories objectAtIndex:section] exerciseCategoryName];
return [self.exercises[currentCategory] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ExercisePrototypeCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
NSString *currentCategory = [[self.exerciseCategories objectAtIndex:indexPath.section] exerciseCategoryName];
MFTExercises *exercise = [self.exercises[currentCategory] objectAtIndex:indexPath.row];
cell.textLabel.text = exercise.exerciseName;
return cell;
}
Using a NSDictionary may or may not benefit your app but you don't have to create an array as instance variable for every body part you have. It may also be more easy to save a single dictionary to disk for persistence.
First of all, you should practice it with WWDC UITableView section. There are many source code that uses UITableView, UICollectionView and UIScrollView.
What you need in that code is you need to return section header for exerciseCategories, you only defined number of section in - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView this delegate function but you are returning all nil value for the section header at the moment.
- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
FNTExerciseCategories *category = [self.exerciseCategories objectAtIndex:section];
return category. exerciseCategoryName;
}
this will display your section. but you need to think about the structure of your data because right now you are not returning correct number for each section you are just returning [self.exercises count] for all section.
And to render the UINavigationController, you need to push the view rather than present view as modal.
[self.navigationController pushViewController:exerciseView animated:YES];

How to create a dynamic size array

What I am trying to achieve:
I have a UITableView and I want to check whether the table was selected or not and keep in an array easy to access the YES or NO values that corresponds to that row so that afterwards i can manipulate the data.
my code as follows
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellLabelText = cell.textLabel.text;
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
selected[row] = NO;
}
else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
selected[row] = YES;
}
}
As it stands out I can create a BOOL selected[some value] but my problem is that the max index needed for me is unknown as my table size changes constantly. thus setting the max index limits me.
I am new to objective C and I come from a PHP background thus I dont know whether it is possible to create an array that does what i want to do in objective-c.
Otherwise what would be my options within objective-c to have an easy way to easy write/read selected[row] = YES/NO.
I need a way to write YES/NO and link it to the indexpath.row
Use an NSMutableSet and store the NSIndexPath of the selected rows. If you select a row you add the path to the set. If you unselect a row, remove the path from the set.
To see if a row is selected, see if the indexPath is in the set or not.
BTW - this only works if the rows are fixed. If the user can add, remove, or reorder rows then this approach will not work. In such a case you need to store data keys, not index paths.
Create an ivar of type NSMutableSet. Let's call it selectedRows:
selectedRows = [[NSMutableSet alloc] init];
Then in didSelectRow you do:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
BOOL selected = [selectedRows containsObject:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellLabelText = cell.textLabel.text;
if (selected) {
cell.accessoryType = UITableViewCellAccessoryNone;
[selectedRows removeObject:indexPath];
} else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[selectedRows addObject:indexPath];
}
}
In your cellForRow... method you do something similar:
BOOL selected = [selectedRows containsObject:indexPath];
cell.accessoryType = selected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
Just use
NSMutableArray *dynamicArray = [NSMutableArray array];
You can add and delete objects from this at will. Just be sure to use the NSNumber wrapper to add primitives:
[dynamicArray addObject:[NSNumber numberWithInt:indexNumber]];
// or
[dynamicArray addObject:#(indexNumber)];
Instead of an array you can use a index set.
#property (nonatomic,strong) NSMutableIndexSet *pickedIndexPaths;
- (void)viewDidLoad
{
_pickedSIndexPaths = [[NSMutableIndexSet alloc] init];
[super viewDidLoad];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//…
if(indexPath.section == 0) {
cell.textLabel.text = self.sports[indexPath.row][#"sport"][#"name"];
if ([_pickedIndexPaths containsIndex:indexPath.row]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
}
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([_pickedIndexPaths containsIndex:indexPath.row]) {
[_pickedIndexPaths removeIndex:indexPath.row];
} else {
[_pickedIndexPaths addIndex:indexPath.row];
}
[tableView reloadData];
}
}
When what you need is a variable length array of boolean values, you can use CFBitVectorRef. This will consume much less memory than using a Cocoa collection designed for objc object values (provided of course that array has many elements) because it consumes 1 bit for each value, rather than a full pointer which points to an individual dynamically allocated reference counted object.

Custom cell in UITableView is not displaying properly - only last cell is populated

Apologies if this comes across as a beginner's question. I'm trying to populate a UITableView with sections and custom cell formatting.
I've created a customCell in ViewControl.xib which sits along the main view and looks like this:
customCell image
I have a dictionary to load up the values using a method in another class, depending on which row it's at. If it's in Row 1, load details for item 1 etc.
This is the code I'm using:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"customCell"];
// assigns current row's labels
NSArray * customCellText = [Model cellText:indexPath.row];
dinnerItem.text = customCellText[0];
dinnerDescription.text = customCellText[1];
dinnerTime.text = customCellText[2];
cell = customCell;
return cell;
}
And this is currently what's being generated:
iPhone simulator screenshot
The issues I have:
It's not populating all rows, only the last one.
I can only seem to click on the row which is populated, and even then
it stays selected as opposed to 'clicking on it'.
If I drag it up or down quickly it crashes.
I presume it has to do with the way it's redrawing/populating cells?
Thanks in advance!
SineTwo
EDIT, ADDED MORE CODE FOR CLARIFICATION:
ViewController.m
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [Model countKeys];
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [Model rowsInSection:section];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
// slightly crap code, this is initiated in viewDidLoad and is an array created by a method in Model.m. Only looks for keys and returns an array.
return headerKeys[section];
}
Model.m
+(NSArray *)headerKeys
{
NSArray *headerKeys = [[NSArray alloc] init];
headerKeys = [timerDictionary allKeys];
NSLog(#"All keys: %#", headerKeys);
return headerKeys;
}
+(NSArray *)customCellText
{
NSArray *customCellText = [[NSArray alloc]initWithObjects: #"dinnerItemText", #"dinnerDescriptionText", #"01:00", nil];
return customCellText;
}
+(NSInteger)rowsInSection:(NSInteger)sectionNumber
{
NSArray *keyContent = [[NSArray alloc] init];
keyContent = [timerDictionary objectForKey:dictionaryKeys[sectionNumber]];
NSLog(#"current section[%i]: %i", sectionNumber, [keyContent count]);
return [keyContent count];
}
+(NSArray *)cellText:(NSInteger)rowNumber
{
// display all dictionary keys, dictionaryKeys[x] will give back the specific category
dictionaryKeys = [timerDictionary allKeys];
// displays contents of first key in dictionary
NSArray *keyContent = [[NSArray alloc] init];
keyContent = [timerDictionary objectForKey:dictionaryKeys[0]];
// creates an array with all items within the selected key
NSArray *keyItems = [[NSArray alloc] initWithArray:keyContent[rowNumber]];
return keyItems;
}
I can say the following:
if you only do that:
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"customCell"];
prior to iOS 5 this doesn't guarantee you getting back a cell. Prior to iOS5 you have to do the following:
NSString *cellIdentifier = [NSString stringWithFormat:#"I%d-%d", indexPath.section, indexPath.row];
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"customCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
// assigns current row's labels
NSArray * customCellText = [Model cellText:indexPath.row];
dinnerItem.text = customCellText[0];
dinnerDescription.text = customCellText[1];
dinnerTime.text = customCellText[2];
cell = customCell;
return cell;
}
If you are on iOS5/6 these lines are not needed anymore:
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
But you have to use the following methods in the tableView setup:
registerClass:forCellReuseIdentifier: (available in iOS6 and later)
registerNib:forCellReuseIdentifier: (available in iOS5 and later)
Just hoping to solve your problem:
Don't forget the lines [...](cell == nil)[...]
First, change your method to this! You need to check if the cell is nil:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"customCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// assigns current row's labels
NSArray * customCellText = [Model cellText:indexPath.row];
dinnerItem.text = customCellText[0];
dinnerDescription.text = customCellText[1];
dinnerTime.text = customCellText[2];
cell = customCell;
}
return cell;
}

I can't load more than 40 UITableViewCells in my UITableView

I am parsing XML that gets the first 25 items in my MySQL database using PHP - LIMIT and GET. When I click on the "Load More" cell that I append to the bottom of my tableview, it successfully gets the next 25 items, but only loads the first 40 and leaves off the last 10. Each time I click on the "Load more" cell it add 25 to my range (ie 0-25,25-50), but it seems that my range caps at 65 and the display caps at 40.
Here is my load more function thats not working:
-(void) getNewRange{
int currentRange = [allItems count];
int newRange = currentRange + 25;
if(newRange > [xmlParser total]){
NSLog(#"evaluating as greater than the total, which is 837");
newRange = [xmlParser total];
}
NSString *range = [[NSString alloc] initWithFormat:#"?range=%d&range2=%d",currentRange,newRange];
NSString *newUrl =[[NSString alloc] initWithFormat:#"http://localhost/fetchAllTitles.php%#",range];
XMLParser *tempParser = [[XMLParser alloc] loadXMLByURL:newUrl];
[allItems addObjectsFromArray:[tempParser people]];
NSMutableArray *newCells = [[NSMutableArray alloc] initWithCapacity:25];
for(int i=currentRange;i<newRange;i++){
NSLog(#"%d",i);
NSIndexPath *indexpath=[NSIndexPath indexPathForRow:i inSection:0];
[newCells addObject:indexpath];
}
NSLog(#"%#",newUrl);
[self.tableView insertRowsAtIndexPaths:newCells withRowAnimation:UITableViewRowAnimationAutomatic];
}
I'm getting closer, but I get this new error:
*** Assertion failure in -[_UITableViewUpdateSupport _computeRowUpdates], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableViewSupport.m:386
Read up on how you can Reuse your table view's cells.
Your data does not need to be 'owned' by the cell.
UITableView isn't a class to contain your data, and you shouldn't try to directly micromanage what cells it displays. As another poster stated, read up on how to use it. What you should do is:
-(void)loadNewData
{
NSIndexPath *index;
XMLParser *tempParser = [[XMLParser alloc] loadXMLByURL:newUrl];
NSArray *people=[tempParser people];
for(id *person in people)
{
[self.dataArray addObject:person];
indexPath=[NSIndexPath indexPathForRow:[self.dataArray indexForObject:person] inSection:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [AnArray count];//use whatever array stores your data
}
//If you've subclassed the cell, adjust appropriately.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell) {
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
//Customize the cell
return cell;
}
The table view will take care of all the logic involved in displaying the cells, if you let it. This way, you only have a limited number of cells taking up memory at any given time, and you don't have to handle that -- the table view automagically handles reusing the cells, and knowing how many are needed as a buffer before / after.
you should not set numberOfRowsInSection inside your method. The number of rows should get returned from the tableView's datasource method - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section. Just return [allItems count] there.

Resources