UITableView cell separators index incorrect? - ios

I am trying to implement a UITableView where the odd indexes are a 'separator'. The problem is, when I have one object in my dataSource and load the tableView, I see the first cell fine. However, when there are two objects in my dataSource and then load the tableView, I only see the one cell even though there should be two cells. I have tried to reloadData to cell if that would change anything and it did not.
If you need code to see, just let me know!

I suspect your problem is in tableView:numberOfRowsInSection:. Because you are inserting "spacer" cells before your table's data cells, the number of rows returned from that method needs to take into account the spacer cells also, so the correct number to return is twice the number of elements in your array.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.cellArray count] * 2;
}

Related

How to reload UITableView section with different row count?

This is a simple idea, but I can't seem to figure out the right solution.
I have a UITableView with many sections, including a header view.
I would like to be able to hide/remove each of the tableview cells in the sections I have, and then reload them back in with a nice animation.
My problem is that the number of sections could be different than before, and the number of rows in the sections could be different than before...
I can't simply fade out the entire tableview, and then reload it, and fade it back in because I need the header view to always be visible and scroll with the tableview.
I need to have them removed because the hiding and showing will be a nice "fade" animation with an activity indicator.
Just use the tableView methods
[self.tableView insertSections:(nonnull NSIndexSet *) withRowAnimation:(UITableViewRowAnimation)]
[self.tableView insertRowsAtIndexPaths:(nonnull NSArray<NSIndexPath *> *) withRowAnimation:(UITableViewRowAnimation)]
They will allow you to animate the insertion of new rows / sections. For Deletion there are similar methods, except they are called "deleteSections..." and "deleteRowsAtIndexPaths...".
Just take care that after insertion / deletion, these tableView's delegate functions return the current values:
- (NSInteger) numberOfSectionsInTableView:(UITableView *)tableView { }
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {}
And "correct" means: Amount of sections / rows before the insertion / deletion, plus or minus the amount of rows and sections after the insertion / deletion.
You can use this:
self.tableView.reloadRows(at: [indexPath], with: .fade)
Here index path is the specific rows that you want to reload. If you are changing the number of rows, you have to change the number of items in the data source. Or you can change the value returned by numberOfRowsInSection
For chaining animations you could use
let pathsForNewCells = [indexPathOne, indexPathTwo, indexPathThree]
myTableView.beginUpdates()
myTableView.insertRows(at: pathsForNewCells, with: [UITableViewRowAnimation])
myTableView.endUpdates()
thus, according to the documentation the height will animate without reloading the cells.

Calling dequeueReusableCellWithIdentifier:cellId inside heightForRowAtIndexPath

I am working on an ios application,
I have a normal table view. When calling heightForRowAtIndexPath I am doing the folowing
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellId = [self getCellIds][indexPath.row];
BaseTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
return [cell calculateHeigh];
}
Basically I am dequeueing the cell because I have a function calculateHeigh inside every cell that will do the height calculation. this is working fine as intended however I have a concern:
Is it safe to call dequeueReusableCellWithIdentifier: inside the heightForRowAtIndexPath ? will it cause any issue?
EDIT:
Just to clarify why I did this, I have a big amount of custom cells with different identifier that needs to be loaded. and to avoid having a huge if-else statement in my heightForRowAtIndexPath I placed the getter of the cell height in the custom cell that way I just ask it to return it (no calculation is made there), I can't do it as a class method as I don't know which class, I can get the object from the identifier and not the class. And I want to avoid a big if-else just for code readability.
So my concern was with the dequeueReusableCellWithIdentifier: is it heavy to call it when getting the height? will it cause memory issues or lags? or is it worth to just do a bug if-else of use a dictionary?
First of all you should avoid any calculations in table drawing methods(such as heightForRow, cellForRow, etc). These methods are called a lot and although your table may be short and/or not complicated(with custom cells with a lot of labels, buttons and images) you should always try to optimize this drawing process or otherwise user will experience some nasty lag when scrolling.
So you should call some method to prepare data before calling 'reloadTable'
-(void)prepareMethod
{
//get only one cell to calculate all row heights
BaseTableViewCell *cell = [_myTableView dequeueReusableCellWithIdentifier:cellId];
for (NSDictionary* dataObj in _dataArray)
{
//loop through all rows data and set new property for row height
dataObj[#"rowHeight"] = [cell calculateHeigh];
}
}
And then when calling heightForRow just pass this value without any expensive operation(such as probably string calculations):
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
//always make sure you don't access unexisting array index
return ( indexPath.row < _dataArray.count ) ? _dataArray[indexPath.row][indexPath.row][#"rowHeight"] : 1.0;
}
Of course you don't need separate method just to populate row heights in your data array - you can populate this value when populating(formatting) your data array to avoid second array iteration. It all depends on your current implementation.
Just remember that expensive drawing methods(not only for table though) should always be as short as possible and just get data needed for drawing and draw. It's really so simple. If you need to make some complicated calculations do it before that(maybe in view init) so your data is prepared before actual drawing. This way your application will be working smoothly even with bigger tables(because no matter how big the table is, UITableViewController draws only visible cells).
Regards,
hris.to
I don't like to have big if statements in heightForRowAtIndexPath and accessing a cell using dequeueReusableCellWithIdentifier. Your approach getting cell height from each cell quite is reasonable. I believe your calculateHeigh return value depends on the table data you pass into the cell.
In BaseTableViewCell.h
+ (CGFloat)heightWithData:(id)data;
In BaseTableViewCell.m
+ (CGFloat)heightWithData:(id)data
{
//put your calculateHeigh logic here. I believe your calculateHeigh depends on the data each cell has.
}
Then you can do
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [BaseTableViewCell heightWithData:[self.tableData objectAtIndex:indexPath.row]];
}
If you do this, you don't need to access each cell object to get cell height.
You should not use this method to provide the calculation. Based on what I can see on your setup, you are calculating the height based on the values already on your cell. What happens is that the cell dequeue system will give you a cell to reuse, but because it's sharing cells from multiple index paths, that cell probably has data that belongs to a record of an index path different from the current one. Get the calculate height code and try to reproduce it inside the datasource callback you are using.

iOS >> UITableView with 2 Different Custom UITableViewCells that Have Different Height

I have a UITableView for which I created two different custom cells, let's call one "RegularCell" and the other "BigCell". The reason to do so is that I need different representation for the data model objects, where in a certain case I wish to present the data differently.
I read a bit about ways to approach it via heightForRowAtIndexPath vs. cellForRowAtIndexPath, but I'm not clear how to approach it in my case >> In my table, I don't know, in advance, which row will include which custom cell; I only get this data in cellForRowAtIndexPath where I check in the data array which case I need to represent for a specific row.
It seems silly to do the calculation in heightForRowAtIndexPath since it's called before cellForRowAtIndexPath and the whole idea there is that you don't create all the cells in advance and just "make room" for things like the scroller size.
On the other hand, only when I realise which content I'm representing, I can tell which cell I require and therefore what should be the row height.
Anyone encountered a case like that and can share some wisdom?
David is right!
I don't agree with you saying
In my table, I don't know, in advance, which row will include which
custom cell; I only get this data in cellForRowAtIndexPath where I
check in the data array which case I need to represent for a specific
row.
in heightForRowAtIndexPath you can access you datasource the same way you do it in cellForRowAtIndexPath:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat bigCellHeight = 80.0;
CGFloar regularCellHeight = 44.0;
MyDataObject *object = [myArray objectAtIndex: indexPath.row];
if ([object anyConditionToChoseBigCell]) {
return bigCellHeight;
}
return regularCellHeight;
}
heightForRowAtIndexPath is called before cellForRowAtIndexPath as the tableView layouter needs to know how big the scroll view is going to be as well as what cells should be visible on screen at the time its going to be displayed, this may vary depending on the height of each cell.
I've come across this problem before and my recommendation is that you calculate the height of each of the cells in your model before your tableView is even run.
I've had similar problem. That's what I ended up with:
-(CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
return cell.frame.size.height;
}
OK...
I found an easy solution - Since I have an array of data objects, I can check per each row, the relevant data object relevant property >> in my case, I have a BOOL value called isSuper >> and in this way I can set the row height per each object in the DB without the need to create or upload an actual UITableViewCell in heightForRowAtIndexPath.

Change the height of a static UITableViewCell only possible by overriding heightForRowAtIndexPath?

I've looked through lots of examples of how to hide a static UITableViewCell by overridding heightForRowAtIndexPath, and while I've now got it working it just seems so cumbersome that I'd like to see if I'm doing something wrong.
I have a UITableViewController with a table view that has about 8 rows. This screen in my app shows a single object, so for example one row is description, one is an image, one holds a map view, etc. All of the rows are static.
In some cases, some of the objects that are shown don't have a map, so I want to hide the row that holds the mapview. Since it's a static row, I was thinking that by having an outlet property for that row (e.g. #property (weak, nonatomic) IBOutlet UITableViewCell *mapViewRow;), then I could somehow set that row's height to 0 or hide that row in viewDidLoad or viewWillAppear. However, it seems like the only way to do this is to override the heightForRowAtIndexPath method, which is kind of annoying because then I need to hardcode the index of the map row in my code, e.g.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 6 && self.displayItem.shouldHideMap) {
return 0;
}
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
Of course, not a big deal, but just the whole way of sizing static rows in a tableview seems like it defeats the point of setting them up in the storyboard in the first place.
EDIT - rationale behind my answer
To change the height of a row you must reload either the whole table or a subset containing that row. B/c it's a bit odd to have a row in the table w/ zero height, I prefer modifying your data source such that the row doesn't exist in the table.
There are a number of ways to do that. You could build an array from your displayItem where each row in the array corresponds to a row in the table w/ appropriate data. You would rebuild this array and then call [tableView reloadData]. My original answer would also eliminate the unwanted row by treating each data element as a section with 0 or 1 rows.
ORIGINAL ANSWER
Is your tableview a plain or grouped style? If it's a plain style, you could treat each row as a section with either 0 or 1 rows in it. In your tableView dataSource and delegate methods you would use the section index to identify the data within self.displayItem that you care about for that section.
Your code would be something like:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 8; // max number of possible rows in table
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger rows = 1;
// set self.mapSectionIndex during initialization or hard code it
if (section == self.mapSectionIndex && self.displayItem.shouldHideMap) {
rows = 0;
}
return rows;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:indexPath
{
return 60.0f; // whatever you want the height to be
}
// also modify tableView:cellForRowAtIndexPath: and any other tableView delegate and dataSource methods appropriately
you can override heightForRowAtIndexPath and just write in it return UITableViewAutomaticDimension; this will make cell calculate the height automatically as UILabel height is >= . it worked for me.

Initial load of UITableView returning default height. heightForRowAtIndexPath IS implemented

In this project I am populating a table with data from a web service. The table reloads as more data becomes available. I am using heightForRowAtIndexPath to size the cell according the size of the title string. It returns the MAX of 54 or string height. The strange thing is that the first time that the table runs through the load sequence it returns 44 for every cell. I log the cell height in cellForRowAtIndexPath. When reload is called a second time I get the correct cell heights. The result is that the cells subviews are out of place initially then put in place very abruptly. Wondering if anyone has any insights about UITableView that may point me in the right direction. Thanks!
You have to store size of string before calling reload in a member variable since the string got first so it will be easier, the next thing is to return that member variable in height for row at indexPath method;
#interface
{
CGSize fixedStringSize;
}
#implementation
-(void)dataFinishDownload
{
fixedStringSize = [yourCustomMethodForStringSize:titleString];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return fixedStringSize.height;
}
#end

Resources