I have created UITableViewController. I have a NSMutableArray with three records. I have created a custom UITableViewCell class and linked it with TableViewCell. But when running the program my tableview displays below output. There are only three records but table view shows 5 rows and all my data are showed in first row itself.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
callRecordsTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CallRecordsCell" forIndexPath:indexPath];
callRecords *record = [self.callRecordsDataController callRecordAtIndex:indexPath.row];
NSLog(#"Indexpath row now is %d",indexPath.row);
cell.callRecordOriginalDestinationLabel.text = record.originalDestination_Alias;
NSDateFormatter *df = [[NSDateFormatter alloc]init];
[df setDateFormat:#"dd-MMM"];
cell.callRecordStartTime.text = [[NSString alloc]initWithString:[df stringFromDate:record.start_Time]];
cell.callRecordCallDuration.text = record.call_Duration;
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
NSLog(#"count is %d",[self.callRecordsDataController callRecordsCount]);
return [self.callRecordsDataController callRecordsCount];
}
DataController code
-(NSUInteger)callRecordsCount{
return [self.callRecordsList count];
}
- (callRecords *)callRecordAtIndex:(NSUInteger)index{
return [self.callRecordsList objectAtIndex:index];
}
I found the problem.it was because of rendering issues in iphone7plus simulator.i ran the same project in other simulators it was displaying it correctly.
!iphone7plus simulator
There are only three records but table view shows 5 rows and all my data are showed in first row itself.can somebody help??
First on all, I DO think the table is showing only 3 records.
Three records ARE NOT displaying in the same row.
My guess,
Back to the storyboard, I think you set the row height, separator and the layout constraints in the cell wrongly.
Please screecap the storyboard and post your cell design.
Also read this tutorial about creating custom cell with storyboard:
https://www.appcoda.com/ios-programming-customize-uitableview-storyboard/
Related
I am currently making an app which displays the user's profile. For that purpose, I used an UITableViewCell with custom cells for the different types of data (phone numbers, mail addresses, etc...). There is a maximum of 8 cells per profile.
The user is allowed to edit its profile in the simplest way. When the tableview's editing mode is triggered, all editable labels are replaced by textfields. And then turned back to labels when the modifications are finished.
Homever, there seems to be a problem with the cells that are not visible. Everytime they reappear in the view, they are reloaded, the setEditing:YES method is triggered again, etc... Because of this, every change made in the textfield is lost.
Is there a way to prevent the tableview to remove the non-visible cells and to add them back ? There are only eight cells, so it wouldn't be very resources consuming, and I wouldn't have to save their state everytime a change is made.
PS : I have tried several things with the dequeueReusableCellWithIdentifier method and the identifiers of each cell, but I have not managed to achieve what I want. Everytime I hide a cell, its content is refreshed.
You should use static cell not dynamic. Select table view and change config like image.
And add cell in interface builder!
In this case, you are not helped with UITableView`s Reusability(Reusability is ofcourse a great thing in most cases) but will have too much difficulty in preserving edits. So you can avoid reusability and prepare your cells before hand.
Add an NSMutableArray iVar or property in your ViewController
#property (nonatomic, strong) NSMutableArray *cells;
In your viewDidLoad: prepare your cells without any reuseIdentifier
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//Creates tableView cells.
[self createCells];
}
- (void)createCells
{
self.cells = [NSMutableArray array];
TCTimeCell *cellCallTime = [[TCTimeCell alloc] initWithTitle:#"CALL" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeCall];
[_cells addObject:cellCallTime];
TCTimeCell *cellLunchOut = [[TCTimeCell alloc] initWithTitle:#"LUNCH START" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeLunchOut];
[_cells addObject:cellLunchOut];
TCTimeCell *cellLunchIn = [[TCTimeCell alloc] initWithTitle:#"LUNCH END" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeLunchIn];
[_cells addObject:cellLunchIn];
TCTimeCell *cellSecondMealOut = [[TCTimeCell alloc] initWithTitle:#"2ND MEAL START" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeSecondMealOut];
[_cells addObject:cellSecondMealOut];
TCTimeCell *cellSecondMealIn = [[TCTimeCell alloc] initWithTitle:#"2ND MEAL END" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeSecondMealIn];
[_cells addObject:cellSecondMealIn];
TCTimeCell *cellWrapTime = [[TCTimeCell alloc] initWithTitle:#"WRAP" forTimecard:_timecard andTimeEntryType:TCTimeEntryTypeWrap];
[_cells addObject:cellWrapTime];
}
You can populate your tableView from this array.
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.cells.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
return self.cells[indexPath.row];
}
If you have a sectioned tableView, you can prepare your cells as array of arrays. In that case, your Data Source methods should look like below
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView{
return [self.cells count];
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return [self.cells[section] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
return self.cells[indexPath.section][indexPath.row];
}
I have a table view which display the contacts from array. I setup the table view delegates by follows.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [contactArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"ContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:simpleTableIdentifier];
}
cell.textLabel.text = [contactArray objectAtIndex:indexPath.row];
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 60.0;
}
But the first cell in the table view always empty. It starts display only from second cell. I thought it may be header view. So I removed the header using the following delegate methods.
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 0.0;
}
But still have the problem. I attached the screenshot about this issue.
Any help will be appreciated.
Your TableView is fine and it is working correctly, this is due to some other problem that is included in iOS 7, that automatically scroll insets. To solve this problem, go to your storyboard and select the viewcontroller in which your TableView is and select the ViewController and select the Properties of that ViewController, and uncheck this checkbox, which is read as Adjust ScrollView Insets. See this screen shot,
Your table is correct.Just your table was auto adjusted by the viewController.
You can write self.automaticallyAdjustsScrollViewInsets = NO;
Your Deduction is wrong your first cell isn't missing, but your tableview has started by 64 points down. So change your frame of your tableview or your tableview constraints accordingly.
Tip : Try setting a background colour when you have to debug things like this to clear your doubts.
I am new to ios programming so bear with me if the question is simple. I have a core data table mapped to a table view controller. The data in it currently looks as follows - there is one prototype cell:
I need to sum up the data by dates and show the details of each date in a different section with the summed up total coming up as the first row. Something like:
My question is is this doable? I am thinking I need to create sections and two prototype cells within each table cell. Would appreciate quick feedback.
Thanks all!
The easy way to do this is using section headers. You can either use a single string (#"%#: %#", date, total) or a wrapper view with a label on the left for the date and on the right for the total.
-(NSString *) tableView:(UITableView *)tv titleForHeaderInSection:(NSInteger)s
{
NSString *dateString = [self dateStringForSection:s];
float total = [self totalForSection:s];
return [NSString stringWithFormat:#"%#: %0.2f", dateString, total];
}
Or
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return [self wrappedHeaderForSection:s];
}
You'll have to implement dateStringForSection: or wrappedHeaderForSection: appropriately, of course.
The easiest way is to style your UITableView to 'UITableViewStyleGrouped'.
UITableView *tab = [[UITableView alloc] initWithFrame:rect style:UITableViewStyleGrouped];
Or you can go to interface builder and in Table View change style from plain to grouped.
The style 'Grouped' divides your table into multiple sections.
The using UITableViewDelegate methods specify all the parameters.
// Tell the number of section in table
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return numberOfSections;
}
//Tell the number of rows in each section
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (section == 0)
{
return 2;
} else if(section == 1)...
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0 && indexPath.row == 0)
{
//Show Amount for Jul 02, 2013
cell.textLabel.text = #"Jul 02, 2013";
cell.detailTextLabel = #"20.35";
}
// Do the same for all rows and section in table.
}
For further reference -
http://mobisoftinfotech.com/iphone-uitableview-tutorial-grouped-table/
You should also definitely check out the Sensible TableView framework. Saves me tons of time when working with Core Data.
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;
}