I have a table view which has prototype cells. How do I set the height of all cells using a specific identifier? For example, I have two cells; one with an idenfier of "cell10" and another with the identifier "cell50". How do I set it so all cells with the identifier "cell10" have a height of 10 while all cells with the identifier "cell50" have a height of 50? Any answers are appreciated. (By the way, I am using Swift 2.)
Within heightForRowAtIndexPath() you can call cellForRowAtIndexPath().
Once you have the cell then you can call reuseIdentifier to get its identifier.
Then return either 10 or 50 based on the identifier
I'm not sure how to do it in swift but in objective C i would do the following things-
Implement the following function of tableview-
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
//your custom cell
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if(cell.reuseIdentifier isEqualToString:#"cell10"){
return 10;
}else{
return 50;
}
}
heightForRowAtIndexPath called first and then cellForRowAtIndexPath get called. So it may be possible that in heightForRowAtIndexPath method we do not get cell or some inconsistency.
You must have some value or field on basis of which you can specify or differentiate cell identifier. You have array of models which may contain that value.
So in heightForRowAtIndexPath(), you can get model from datasource array for that indexpath using objectAtIndex method, and check which type it is and depending on that return the height.
This will help in every case. It is working for me.
Related
I have an UITableView with ±10 different UITableViewCells to display full information about an object (description cell, photos cell etc.). So when UITableView is loaded, I do not need UITableView cells to be reused. Wouldn't performance be better if I somehow store UITableViewCells and prevent cellForRowAtIndexPath from being called? If so, what is the way to achieve alike behaviour?
First of all, you can not prevent cellForRowAtIndexPath from being called if you are gonna use UITableView. It is a UITableViewDataSource function and it's not an optional one. Otherwise, you won't be able to populate your tableview.
What you can do is use switch case on indexpath.row in cellForRowAtIndexPath and return necessary cell.
You could try by increasing prototype cell in Storyboard. each cell assign new cell identifier and you need to keep array of identifier matching their index with storyboard.
Your cellForRowAtIndexpath will reduced to:
- (UITableViewCell *)tableView:(UITableView *)tableView1 cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView1 dequeueReusableCellWithIdentifier:[cellIdArray objectAtIndex:indexPath.row] forIndexPath:indexPath];
return cell;
}
Note: cellForRowAtIndexpath method will always called. In above case you can put your data in storyboard itself.
Check similar case at https://www.appcoda.com/sidebar-menu-swift/
I want to know if I have customized my own tableviewcell with a specified height and this value is not the same as I used in heightForRowAtIndexPath. Which height will be used for the cell. Since I have multiple customized cells in one tableview, it will be difficult for me to distinguish each type of them in the heightForRowAtIndexPath function. I just want to know in this case, can I just use the cell height I defined for each type of cell?
You can implement this method of the UITableViewDelegate
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
//The height you desire to have for the row at *indexPath*
}
According to the UITableViewDelegate protocol reference,
The method allows the delegate to specify rows with varying heights. If this method is implemented, the value it returns overrides the value specified for the rowHeight property of UITableView for the given row.
My found is that before iOS8, you have to specify the row height in heightForRowAtIndexPath. Until iOS8, Apple provides autolayout and resize of cell, e.g. adding constraints.
Not sure if this is the only way to control the cell row height or not.
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.
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.
I adjust the height of a custom UITableViewCell inside the custom class, and I believe I need to use the -tableView:cellForRowAtIndexPath: method to adjust the height of the cell. I am attempting to just adjust the height of the custom cell in the custom cell class, then grab the cell at the given index path cast it, and return the height of that cell like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
{
CustomUITableViewCell *cell = (CustomUITableViewCell *)[tableView cellForRowAtIndexPath:indexPath];
return cell.frame.size.height;
}
But I am getting stack overflow. What is a better way around doing this?
The table view delegate will first call heightForRowAtIndexPath: and then the datasource will construct the cell in cellForRowAtIndexPath: after that based on the computed information.
Therefore your approach will not work.
You need to have some logic for computing the height. (E.g. if you are displaying text the height might be dynamic and depend on the amount of text - you could calculate that with an NSString method.)
If you are just displaying a few types of cells with fixed heights, simply define these heights as constants and return the correct height based on the same logic you have in cellForRowAtIndexPath: to decide which cell to use.
#define kBasicCellHeight 50
#define kAdvancedCellHeight 100
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (needToUseBasicCellAtThisIndexPath) {
return kBasicCellHeight;
}
return kAdvancedCellHeight;
}
If it's a storyboard cell, you can call dequeueReusableCellWithIdentifier:. Otherwise, you can just instantiate the cell directly with something like [CustomUITableViewCell alloc] initWithFrame:].
I use this approach (using a prototype cell to calculate height) myself because it allows our designer to modify storyboard cells without requiring code changes.
You may want to adjust your approach based on whether the height is static or dynamic as discussed here.