I've viewed all of the other questions on this topic and they seem to have issues that do not pertain to my code. I've struggled with this for a while and am hoping that another set of eyes will see where I have gone wrong. I have put breakpoints in various areas of the code. There is a breakpoint with NSLog(#"number of rows, %lu",(unsigned long)self.eventContent.count); in the "TableView numberOfRowsInSection" Section that returns: "number of rows,3". I also have a breakpoint in the "tableView cellForRowAtIndexPath" Section with NSLog(#"cellForRowAtIndexPath. %lu",(long)indexPath.row); which returns:
"cellForRowAtIndexPath. 0"
"cellForRowAtIndexPath. 1"
The error occurs at 1 on the line beginning with cell.titleLabel.text. When I take out the comments on the NSLogs following, I can see that the first cell generates o.k.
Can someone please direct me to where else I might look?
So thanking you in advance...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of rows, %lu",(unsigned long)self.eventContent.count);
return self.eventContent.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"eventCell";
EQCalendarCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Get the event at the row selected and display its title
NSLog(#"cellForRowAtIndexPath. %lu",(long)indexPath.row);
cell.titleLabel.text = [[[eventContent objectAtIndex:indexPath.section]objectForKey:#"title"]objectAtIndex:indexPath.row]; //Fails here
cell.notesLabel.text = [[[eventContent objectAtIndex:indexPath.section]objectForKey:#"notes"]objectAtIndex:indexPath.row];
cell.startTimeLabel.text = [[[eventContent objectAtIndex:indexPath.section]objectForKey:#"startTime"]objectAtIndex:indexPath.row];
cell.endTimeLabel.text = [[[eventContent objectAtIndex:indexPath.section]objectForKey:#"endTime"]objectAtIndex:indexPath.row];
return cell;
In number of rows in section you're using the self.eventsList array, whereas in your cellForRow method you're asking for the objectAtIndex: of the eventContent array. Chances are you messed up the two, because eventContent clearly does not have as many rows as the eventsList
Related
I want to display webservies array values in tableview for each cell i need to display two values Ex:total ten values mean each cell display 2 values in each row. webservies total value nine means each display two values last cell display one value. how can i achieve this help me. new for development.
Follow this tutorial for custom cell and design it the way you want:
Crafting Custom UITableView Cells
This is the method where you can set the values for the custom cell labels from each array:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
return cell;
}
Create a custom class of UITableViewCell say CustomCell and add 2 labels to it both occupying half of the space or as per your design what you need. Now say that they are labelOne and labelTwo.
From your Controller class you got the array of objects that you need to display in lables. In UITableViewDataSource method use this code
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return (dataArray.count+1)/2; //This will provide correct row count for odd data set, such as when count is 9
}
and use this code to populate cell label text
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell==nil) {
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"customCell"];
}
cell.lableOne.text = [dataArray objectAtIndex:indexPath.row*2];
if((indexPath.row*2)+1 < dataArray.count){
cell.lableTwo.text = [dataArray objectAtIndex:(indexPath.row*2)+1];
}
return cell;
}
In cellForRow: you should send the cell the values from the array at [yourArray objectAtIndex:(indexPath.row * 2)] and [yourArray objectAtIndex:(indexPath.row * 2 + 1)]. so row 6 will get the objects for indexes 12 and 13.
Also you should always check if the objects exists. something like - if (yourArray.count > (indexPath.row * 2)) and if (yourArray.count > (indexPath.row * 2 + 1)) than send to cell else don't. (than you will get one object in cell when you get odd number of values.
Using % fetch the number of rows for the table view. Then for the last cell if value is null then display blank... you can have a basic logic for that. Take 2 lbls in each cell and display on those lables which contains value in it.
I am creating an app where users can store an object in Core Data. I have the Objects being pulled into a UITableView and everything is working correctly there. I now want to separate the objects into a possible of 1-3 different sections based on choices in a UISegmentedControl.
Currently I have this to create the 1 section and populate the cells of that section with my objects
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return self.fetchedDiscs.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"DiscCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//Configure Cell
Disc *currentDisc = [self.fetchedDiscs objectAtIndex:indexPath.row];
cell.textLabel.text = currentDisc.name;
cell.detailTextLabel.text = currentDisc.brand;
return cell;
}
So the main question is how to dynamically change the number of sections and number of rows in section?
The Segmented control returns values such as Option 1, Option 2, Option 3. The only way I can think of is to loop through my fetchedDiscs array and separate that into an array for each section. Then I can return the number of arrays if they exists and I can get the count of each array to get the number of rows in each section. But then I get to the problem of How to get the CellForRowAtIndextPath to work correctly with three arrays.
Basically there has to be a better more logical way to do this. I am just not sure how.
Let, you have a dictionary named dataSource that contains 'x' number of array as value for key "key1", "key2", .. "keyX". You can do this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[dataSource allKeys] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//Get the array object form key. I am considering 'keySection' as an example
return [[dataSource objectForKey:#"keySection"] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"DiscCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.textLabel.text = [[dataSource objectForKey:keySection] objectAtIndex:indexPath.row]
return cell;
}
Note: I wrote the code in this editor, excuse any mistakes, I just tried to share the idea. Hope this helps.. :)
I have a UITableViewController created in storyboard. It has two sections. The first section's rows contain controls laid-out in storyboard. I want to update the rows in the second section using values in an array.
I'm fairly new to iOS development. I understand how to use a UITableViewDataSource to update a table based on the array, but not how to restrict the updates to a specific section. Can anyone outline how to do this?
EDIT This seemed like a simple problem, so I thought I code would just obscure the question. Maybe I was wrong. Heres what I have:
My numberOfRowsInSection function returns 1 in the section number is 0, because the first section (the one I designed in storyboard) has a single row, otherwise it returns the number of elements in the backing data array:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0)
return 1;
else
return [myData length];
}
My cellForRowAtIndexPath function creates a cell if the section number is 1. But I don't know what to do if the section number is zero. How do I avoid having to recreate the rows I laid-out in storyboard?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.section == 1)
{
cell.textLabel.text = [myData objectAtindex:indexPath.row];
}
else
{
// What to do here?
}
}
Well If you only have few static controls in the first section why won't you put these controls in a table header view instead? Thus you'll only have one section to worry about :)
In your method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPathadd this
Create 2 differents UITableViewCells and reference them like this
if (indexPath.section == 1) {
NSString *CellIdentifier = #"DynamicCell";
VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//You are drawing your second section so you can use your array as values
cell.property1...
cell.property2...
cell.property3...
return cell;
}else{//If you have only 2 sections then else represent your first section
//You are drawing your first section
NSString *CellIdentifier = #"StaticCell";
VideoCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
You can change the row value in the delegate method
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
To identify the section, just use:
indexPath.section
You can use reloadRowsAtIndexPaths: with an array of all the indexPaths that are in the wanted section, built with a loop and a NSMutableArray.
- (void)reloadSections:(NSIndexSet *)sections
withRowAnimation:(UITableViewRowAnimation)animation;
The parameter "section" is An index set identifying the sections to reload.
I´m quite new to iOS development and I´m having a terrible time by trying something that should be easy; to add an extra row in a TableView everytime the user clicks on one of the existing rows. There is no real purpose on that action, I´m just wanting to understand the behaviour of TableView.
So I did the following:
I used a Split View-based template and changed the number of rows to 30 in the RootViewController.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 30;
}
The method tableView:didSelectRowAtIndexPath looks in the following manner:
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
NSMutableArray* paths = [[NSMutableArray alloc] init];
NSIndexPath *indice = [NSIndexPath indexPathForRow:30 inSection:0];
[paths addObject:indice];
detailViewController.detailItem = [NSString stringWithFormat:#"Second Story Element %d with all its information and bla bla bla", indexPath.row];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
When I execute the program and click on one of the elements, I receive the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (30) must be equal to the number of rows contained in that section before the update (30), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted).'
I did not change any other part of the code that the template provides.
I read quite extensively the documentation from Apple and the responses to the following questions:
Add a row dynamically in TableView of iphone
and
how to properly use insertRowsAtIndexPaths?
The second question seems to address the same problem, but I´m not capable to understand what is happening. What do they mean with dataSource? The response that I understand better says the following:
It's a two step process:
First update your data source so numberOfRowsInSection and cellForRowAtIndexPath will return the correct values for your post-insert data. You must do this before you insert or delete rows or you will see the "invalid number of rows" error that you're getting.
What does this update of the data source implies?
Sample code would be HIGHLY appreciated, because I´m totally frustrated.
By the way, all that I´m trying has nothing to do with entering the editing mode, has it?
You need to keep the count returned by tableView:numberOfRowsInSection: in sync!
So when you have 30 rows and then tell the tableview to insert a new row you need to make sure tableView:numberOfRowsInSection: will now return 31.
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section
{
return self.rowCount;
}
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rowCount++;
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
In practice you would probably use an array to track your rows return [self.rows count]; etc
The answer is quite simple. When you want to modify a table view you need to perform two simple steps:
Deal with the model
Deal with table animation
You already perform the second step. But you have missed the first one. Usually when you deal with a table you pass it a data source. In other words some data to display within it.
A simple example is using a NSMutableArray (it's dynamic as the name suggests) that contains dummy data.
For example, create a property like the following in .h
#property (nonatomic, strong) NSMutableArray* myDataSource;
and in .m synthesize it as:
#synthesize myDataSource;
Now, you can alloc-init that array and populate it as the following (for example in viewDidLoad method of your controller).
self.myDataSource = [[NSMutableArray alloc] init];
[self.myDataSource addObject:#"First"];
[self.myDataSource addObject:#"Second"];
Then, instead of hardcoding the number of rows you will display (30 in your case), you can do the following:
- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
return [self.myDataSource count];
}
Now, in you didSelectRowAtIndexPath delegate you can add a third element.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[self.myDataSource addObject:#"Third"];
[[self tableView] beginUpdates];
[self.tableView insertRowsAtIndexPaths:(NSArray *) paths withRowAnimation:UITableViewRowAnimationNone];
[[self tableView] endUpdates];
}
It looks like one big problem is with tableView:numberOfRowsInSection:. You need to return the correct number of rows in that method.
To do that, it's usually best to maintain an NSArray or NSMutableArray of items for the table view so in that function, you can say: return [arrayOfValues count];. Keep the array as a property of your view controller class so that it's readily accessible in all methods.
The array can also be used in cellForRowAtIndexPath:. If you have an array of NSString, you can say cell.text = [arrayOfValues objectAtRow:indexPath.row];.
Then, when you want to add an item to the table view, you can just add it to the array and reload the table, e.g. [tableView reloadData];.
Try implementing this concept and let me know how it goes.
You can Also do that for dayanamic table cell
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arrayStationStore count];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentyfire;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentyfire];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentyfire];
}
cell.textLabel.text = [arrayStationStore objectAtIndex:indexPath.row];
return cell;
}
-(NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Check if current row is selected
BOOL isSelected = NO;
if([tblStationName cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark)
{
isSelected = YES;
}
if(isSelected)
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
[arrayReplace removeObject:indexPath];
NSLog(#"array replace remove is %# ",arrayReplace);
}
else
{
[tblStationName cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[arrayReplace addObject:indexPath];
NSLog(#"array replace add is %# ",arrayReplace);
}
return indexPath;
}
I have a tableView with sections, which could be opened and closed. So, when I tap on a section to open it, it is getting filled up with cells and -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) get called exactly as much times as I provided in -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section.
Is that correct? Shouldn't it be just number of visible cells?
Because in my case I have bad situation: I have a lot of custom cells (50~100 cells) and calling -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) for each cell slows down the opening of a section, cause each time reading from nib is performed and cell content is being populated with image.
I've check visibility of cell inside -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) like this:
if ([[self.tableView indexPathsForVisibleRows] containsObject:indexPath])
NSLog(#"visible %#", indexPath);
and it shows that from out of 45 cells, only 6 or 7 are visible. Others are out of visible area. But creating cells still performed.
Here is the code:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"IVCell";
IVCamera *camera = [server.cameras objectAtIndex:indexPath.row];
IVServerListViewCell *cell = (IVServerListViewCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"IVServerListCell" owner:self options:nil];
cell = (IVServerListViewCell*)_tableViewCell;
self.tableViewCell = nil;
}
[cell textLabel].text = camera.cameraName;
cell.preview = camera.preview;
cell.userData = camera;
cell.isEnabled = (server.isInactive)?NO:camera.isOnline;
return cell;
}
Is it still correct? Or am I missing something?
increase your
estimatedRowHeight of UITableview.
Well, I somehow dealt with my problem. Here are my ideas and thoughts how I came to the solution. Maybe it could be helpful to somebody.
I've instructed memory allocations and call stack using Instruments during opening section events. It showed me, that the majority of time is spent on loading cell from nib file.
Firstly, that I've done was reducing the size of nib file, i.e. minimizing the number of views used in custom tableview cell (now its only 2 views and 2 labels, instead of 6 views, 2 images and 2 labels before). It gave me some improve in cells loading. Apple documentation suggests to use as few as possible views and do not use transparency. So be attentive to these suggestions.
Secondly, as I discovered earlier, that not all cell are visible which are created by -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *), I decided to reduce somehow the number of loadings new cells from nib file. To achieve this, I've came to simple idea: return blank default cells for invisible rows, while load custom cells from nib for visible ones. Here is the piece of code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self index:indexPath isInvisibleInTableView:tableView])
return [self getBlankCellForTableView:tableView];
// the rest of the method is the same
...
}
-(BOOL)index:(NSIndexPath*)indexPath isInvisibleInTableView:(UITableView*)tableView
{
NSMutableArray *visibleIndexPaths = [self getExtendedVisibleIndexPathsForTableView:tableView];
return ![visibleIndexPaths containsObject:indexPath];
}
-(UITableViewCell*)getBlankCellForTableView:(UITableView*)tableView
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"IVBlankCell"];
if (!cell)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"IVBlankCell"] autorelease];
return cell;
}
As you can see, I'm not using just -(NSArray*)indexPathsForVisibleRows method of tableview for detecting visible cells. Instead, I've wrote my own method -(NSMutableArray*)getExtendedVisibleIndexPathsForTableView:(UITableView*)tableView. It was necessary because for some reason, when using -(NSArray*)indexPathsForVisibleRows the cells that are next to the last one visible cell or the cells that are previous to the first one visible cell were created as blank cells and looked like empty cells while scrolling. To overcome this, in -(NSMutableArray*)getExtendedVisibleIndexPathsForTableView: (UITableView*)tableView i'm adding border cells to the visible array cells:
-(NSMutableArray*)getExtendedVisibleIndexPathsForTableView:(UITableView*)tableView{
NSArray *visibleIPs = [tableView indexPathsForVisibleRows];
if (!visibleIPs || ![visibleIPs count])
return [NSMutableArray array];
NSIndexPath *firstVisibleIP = [visibleIPs objectAtIndex:0];
NSIndexPath *lastVisibleIP = [visibleIPs objectAtIndex:[visibleIPs count]-1];
NSIndexPath *prevIndex = ([firstVisibleIP row])?[NSIndexPath indexPathForRow:[firstVisibleIP row]-1 inSection:[firstVisibleIP section]]:nil;
NSIndexPath *nextIndex = [NSIndexPath indexPathForRow:[lastVisibleIP row]+1 inSection:[lastVisibleIP section]];
NSMutableArray *exVisibleIndexPaths = [NSMutableArray arrayWithArray:[tableView indexPathsForVisibleRows]];
if (prevIndex)
[exVisibleIndexPaths addObject:prevIndex];
[exVisibleIndexPaths addObject:nextIndex];
return exVisibleIndexPaths;
}
Thereby, I've reduced the time of opening sections with large number of custom cells, which was proved by Instruments tracing and felt while experiencing the app.
Simply add estimated height for UITableViewCell
Problem In my case was: cellforRowAtIndexPath was getting called array.count number of times, whereas, displayed cells where less than array.count.
To resolve this issue, I have just replaced,
(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
with,
(CGFloat)tableView:(UITableView )tableView estimatedHeightForRowAtIndexPath:(nonnull NSIndexPath )indexPath;
check your tableview size.
may be that your tableview height is very large that it keep loading cells until your cell fills all tableview size..
This seems correct yes. the idea about optimizing the loading itself lies within how "dequeueReusableCellWithIdentifier" works.
if u are loading the image from a remote location this is where u would want to optimize the code. but not from the loading of cells as this looks correct here.
I used some similar technique but since indexPathsForVisibleRows is sorted you don't need to use containsObject. Instead you can just do:
//
// Checks if indexPath is visible in current scroll state, we are expanding bounds by 1
// because the cells that are next to the last one visible or the cells that are previous
// to the first one visible could look empty while scrolling.
//
- (BOOL)isIndexPathVisible:(NSIndexPath *)indexPath
{
NSInteger row = [indexPath row];
NSArray *visible = [self.tableView indexPathsForVisibleRows];
NSInteger count = [visible count];
NSInteger first = (count > 0) ? MAX([visible[0] row] - 1, 0): 0;
NSInteger last = (count > 1) ? [visible[1] row] + 1: first + 2;
return row >= first && row <= last;
}
By the way; this assumes that you are using only one section. It won't work for more than one section.
Adding an else solved my problem.
Where I reseted any changes that were made to the cell.
if (! self.cell) {
self.cell = [[LanguageCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
self.cell.accessoryType = UITableViewCellAccessoryNone;
}
else
{
self.cell.checkImage.image = NO;
}