my platform is ios8 and xcode 6.3.1
tableview's delegate like this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
so, the delegate of heightForRowAtIndexPath: should be execute three times , but my code execute four, why ?
My code :
init tableView
- (void)setupTableView {
_selectTableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
_selectTableView.delegate = self;
_selectTableView.dataSource = self;
_selectTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.view addSubview:_selectTableView];
}
other delegate method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSInteger section = indexPath.section;
static NSString *identified = #"selectCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identified];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identified];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
return [self cellWith:cell andSection:section];
}
- (UITableViewCell *)cellWith:(UITableViewCell *)cell andSection:(NSInteger)section {
....
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
CGFloat height = 0;
if (section != SVCellTypeHot) {
height = 5;
}
return height;
}
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
UIView *footerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, kScreenSize.width, 5)];
[footerView setBackgroundColor:[UIColor lightGrayColor]];
return footerView;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = 0;
switch (indexPath.section) {
case SVCellTypeBanner:
{
height = kHeaderViewHeigth;
}
break;
case SVCellTypeRecommand:
{
height = kRecommandViewHeight;
}
break;
case SVCellTypeHot:
{
height = kHotViewHeight;
}
break;
default:
break;
}
return height;
}
heightForRowAtIndexPath 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. There is no guarentee that this method can only be called 'section count * item count' times in the UITableView. As you can tell from its name, it will calculate the height for the cells at IndexPath, so combining the re-using technique, this method will be called many times, as long as it needs to calculate the height for cell at IndexPath
So actually, it is a system behaviour to decide how many times it should be called and when. In your comment, it seems like something changed in indexPath {1-0} so heightForRowAtIndexPath is called twice for {1-0}. You might need to check have you changed any content that cause iOS to re-calculate the cell's height.
Without knowing more details, this is the best we can do to provide you some clues to debug. However, you should not rely on how many times it calls heightForRowAtIndexPath, again, this can be called at any time, as long as you scroll or change any frame inside that cell
heightForRowAtIndexPath: will execute as many times as it needs to. If you are scrolling, for example, it will execute as offscreen cells are about to come onscreen. That method should always be able to provide the correct height and you normally shouldn't be concerned with how often it's called. cellForRowAtIndexPath: executes 3 times as it should.
Related
I am calculating height of Cell programatically in heightForRowAtIndexPath-
My complete method is:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
id cell = [self.tableView dequeueReusableCellWithIdentifier:kCellID];
if([cell isKindOfClass:[Custom class]])
{
return 100;
}
else
{
return 20;
}
}
My app takes 3-5 sec to Show the ViewController. Although cellForRowAtIndexPath is called and I can show the logs in Console.
But My ViewController not loaded.
When just return the height using this code app works fine:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 100;
}
I don't know that is the issue with this line:
id cell = [self.tableView dequeueReusableCellWithIdentifier:kCellID];
What I am missing in this issue?
You have some logic in the cellForRowAtIndexPath: method that determines what type of cell that row should be. Add that logic into the heightForRowAtIndexPath: method to determine the what the height of each row should be.
Your this line is wrong
id cell = [self.tableView dequeueReusableCellWithIdentifier:kCellID];
it will force your cell to reuse.
Use
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
id cell=[tableView cellForRowAtIndexPath:indexPath];
if([cell isKindOfClass:[CustomCellClass class]])
{
return 100;
}
else
{
return 20;
}
}
Here's what I'm trying to do.
I have a UITableViewCell lets say with fixed height of 300 (it is actually a variable size height but I'm trying to simplify the example)
What I want to achieve is that when I scroll back up - I will have a "thumbnailed" version of the cell - with height of 75
I managed to make it happen, but now the problem is that when I scroll up the previous cell heights are adjusted and the scroll position "jumps" once the cell sizes are smaller, which causes the view to "jump back down" when he scrolls up.
How can I adjust it?
The code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell;
if (indexPath.row < lastViewedChapter)
{
cell = [self generateChapterCell:tableView indexPath:indexPath collapsed:YES];
}
else
{
cell = [self generateChapterCell:tableView indexPath:indexPath collapsed:NO];
if (indexPath.row > lastViewedChapter)
{
lastViewedChapter = indexPath.row;
}
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row < lastViewedChapter)
{
return 73;
}
else
{
return 300; //actually here is a code that calculates the height
}
}
You've reduced height of the upper cell and then other cells moved up to fill that space while you were still scrolling right?
Try to set new tableView.contentOffset when you change the cell's height.
In your case the contentOffset.y should be (old contentOffset.y - (300 - 73)) when you return the cell's height as 73.
I didn't test on this but I think it may help and you must calculate new contentOffset for other case too (when scroll down, when table reload data).
static NSInteger _lastRow = -1;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (_lastRow == -1) {
_lastRow = indexPath.row;
return 300;
} else {
if (_lastRow > indexPath.row) {
_lastRow = indexPath.row;
if ([tableView rectForRowAtIndexPath:indexPath].size.height == 300) {
[tableView setContentOffset:CGPointMake(tableView.contentOffset.x, (tableView.contentOffset.y - (300 - 73)))];
}
return 73;
} else {
_lastRow = indexPath.row;
return 300;
}
}
}
This code work fine but still has some bugs (the first row height when first load data is like you have scroll up to it once, when you scroll up to top fast it bounced not normally) but I hope this should help you.
This is something that will definitely happen since you have changed cell heights.
The question is how to mitigate this kind of bad user experience.
UITableView are subclassed from UIScrollView. UIScrollViews provide delegate which is available in UITableView class as well.
Do the following.
self.tableView.delegate = self;
And then implement the following function. In the following, location is a CGPoint variable defined in your header.
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
location = tableView.contentOffset;
}
-(void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
CGPoint newLocation = tableView.contentOffset;
if (CGPointEqualToPoint(location, newLocation))
{
NSLog(#"are equal");
tableView.contentOffset = CGPointMake(location.x, location.y-227);
}
}
I am setting the width and height of UITableViewCell in the delegate methods but the when i check the cell frame in cellForRowAtIndexPath method then its totally different. Below is my Code. I am doing every thing programatically.
self.myTable = [[UITableView alloc] initWithFrame:CGRectMake(self.view.frame.size.width * 0.05,self.view.frame.size.height * 0.08,self.view.frame.size.width * 0.90,
(self.view.frame.size.height * 0.90) - tabBarHeight ) style:UITableViewStylePlain];
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1.0;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return 5;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath.row == 0 || indexPath.row == 4){
return self.myTable.frame.size.height * 0.08;
}
return (self.myTable.frame.size.height * 0.28);
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if(!cell){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
if(indexPath.row != 4){
cell.userInteractionEnabled = NO;
}
switch (indexPath.row) {
case 0:{
NSLog(#"0th cell Frame : %#", NSStringFromCGRect(cell.frame));
break;
}
case 1:{
NSLog(#"1st cell Frame : %#", NSStringFromCGRect(cell.frame));
break;
}
case 2:{
NSLog(#"2nd cell Frame : %#", NSStringFromCGRect(cell.frame));
break;
}
case 3:{
NSLog(#"3rd cell Frame : %#", NSStringFromCGRect(cell.frame));
break;
}
case 4:{
NSLog(#"4rth cell Frame : %#", NSStringFromCGRect(cell.frame));
break;
}
default:
break;
}
return cell;
}
The output for the cell frame i am getting is {{0, 0}, {320, 44}}. But this cell frame is not correct. The Height should be 129 and the width should be something like 288. Can some one guide me what i am doing wrong here?
However, if you still need your given (predefined) size for your cell, you can always use,
For width,
With case of a custom cell, you can make a method like this,
- (CGFloat)cellWidth {
return [UIScreen mainScreen].bounds.size.width;
}
Without custom cell, you can always make a function within the same class where you're creating the UITableView,
- (CGFloat)cellWidth {
return yourTableView.frame.size.width;
}
which will return exact size of your table cell. (I'm always prefer second method because sometime you just don't create device width cells!)
For height,
With case of custom cell, you can make a class method like this,
+(CGFloat)myCellHeight {
return 45.f;
}
Without custom cell, you can always make a function within the same class where you're creating the UITableView,
-(CGFloat)myCellHeight {
return 45.f;
}
which you can use in your UIViewController, with cell class name (for custom cell), [cellClassName myCellHeight] or [self myCellHeight] (default cell in UIViewController) in table's - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath datasource method.
These ways you'll have your cells exact width/height, whenever you want ! :)
Nothing, your code is ok. Your cells will have the size you want. The problem is your cells don't know them final size in this methods cellForRowAtIndexPath. In this methods they think, they have the default size. But before they appear they will be resized. If you define your cells by code, you need use de dimension you expect,this is the tableView width and the height of the methods (heightForCell or estimateHeightForCell). You can check this, by comment this line, and pressing in the delegate: (also you can see).
Line to comment to check:
....
if(indexPath.row != 4){
// cell.userInteractionEnabled = NO;
}
.....
Implement to check:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"cell pressed Frame : %#", NSStringFromCGRect(cell.bounds));
}
- (void)viewDidLoad
{
self.myTable = [[UITableView alloc] initWithFrame:CGRectMake(self.view.frame.size.width * 0.05,self.view.frame.size.height * 0.08,self.view.frame.size.width * 0.90, // set the frame for your tableview
(self.view.frame.size.height * 0.90) - tabBarHeight ) style:UITableViewStylePlain];
self.myTable.delegate = self;
self.myTable.dataSource = self;
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
I am adding one more section to the existing tableView and getting this:
my new cell is reduced by height. Appropriate methods:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return cells[indexPath.section][indexPath.row];
}
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if ([headers[section] isKindOfClass:[UIView class]])
return [headers[section] frame].size.height;
return 10.0f;
}
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if ([headers[section] isKindOfClass:[UIView class]])
return headers[section];
return nil;
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = cells[indexPath.section][indexPath.row];
if (cell == clientXibCell) return 100.0f;
if (cell == agencyXibCell) return 145.0f;
return 46.0f;
}
I can't understand what I need to do to fix this. Any ideas where the source of issue can be?
Update
I am now sure that predefining custom cell visual interface making this trouble.
supervisorCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:nil];
bgView = [[UIImageView alloc] initWithFrame:supervisorCell.backgroundView.frame];
[bgView setImage:stretchableImageByHorizontal([UIImage imageNamed:#"cell_bgd_bottom"])];
[supervisorCell setBackgroundView:bgView];
bgView = [[UIImageView alloc] initWithFrame:supervisorCell.backgroundView.frame];
[bgView setImage:stretchableImageByHorizontal([UIImage imageNamed:#"cell_bgd_bottom_active"])];
[supervisorCell setSelectedBackgroundView:bgView];
When I am uncommenting everything except first statement of creating the cell, everything works fine except custom appearance of cell. What do I need to change in this simple code to fix this?
The height of your cells are controlled by the heightForRowAtIndexPath:. Taking a look on your code, it seems that this method is always returning 46.
Your two ifs are comparing pointers, i. e., instancies of your cells. It means that of all your cells, one will have the height 100, one 145 and all others 46.f.
I think what are you trying to accomplish is set this height for all cells of the same kind, so you should change your heightForRowAtIndexPath: method, like below:
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ( [cell isKindOfClass:[YourCustomCell1 class]] ) return 100.0f;
if ( [cell isKindOfClass:[YourCustomCell2 class]] ) return 145.0f;
return 46.0f;
}
Ps1: Change YourCustomCell class for your own classes. If you don't have subclasses, try to set tags or something like that to differentiate them.
Ps2: always use tableview's method cellForRowAtIndexPath to get the reference of the cell by the indexPath.
When using "Dynamic Prototypes" for specifying UITableView content on the storyboard, there is a "Row Height" property that can be set to Custom.
When instantiating cells, this custom row height is not taken into account. This makes sense, since which prototype cell I use is decided by my application code at the time when the cell is to be instantiated. To instantiate all cells when calculating layout would introduce a performance penalty, so I understand why that cannot be done.
The question then, can I somehow retrieve the height given a cell reuse identifier, e.g.
[myTableView heightForCellWithReuseIdentifier:#"MyCellPrototype"];
or something along that line? Or do I have to duplicate the explicit row heights in my application code, with the maintenance burden that follows?
Solved, with the help of #TimothyMoose:
The heights are stored in the cells themselves, which means the only way of getting the heights is to instantiate the prototypes. One way of doing this is to pre-dequeue the cells outside of the normal cell callback method. Here is my small POC, which works:
#import "ViewController.h"
#interface ViewController () {
NSDictionary* heights;
}
#end
#implementation ViewController
- (NSString*) _reusableIdentifierForIndexPath:(NSIndexPath *)indexPath
{
return [NSString stringWithFormat:#"C%d", indexPath.row];
}
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(!heights) {
NSMutableDictionary* hts = [NSMutableDictionary dictionary];
for(NSString* reusableIdentifier in [NSArray arrayWithObjects:#"C0", #"C1", #"C2", nil]) {
CGFloat height = [[tableView dequeueReusableCellWithIdentifier:reusableIdentifier] bounds].size.height;
hts[reusableIdentifier] = [NSNumber numberWithFloat:height];
}
heights = [hts copy];
}
NSString* prototype = [self _reusableIdentifierForIndexPath:indexPath];
return [heights[prototype] floatValue];
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString* prototype = [self _reusableIdentifierForIndexPath:indexPath];
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:prototype];
return cell;
}
#end
For static (non-data-driven) height, you can just dequeue the cell once and store the height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSNumber *height;
if (!height) {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"MyCustomCell"];
height = #(cell.bounds.size.height);
}
return [height floatValue];
}
For dynamic (data-driven) height, you can store a prototype cell in the view controller and add a method to the cell's class that calculates the height, taking into account the default content of the prototype instance, such as subview placement, fonts, etc.:
- (MyCustomCell *)prototypeCell
{
if (!_prototypeCell) {
_prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:#"MyCustomCell"];
}
return _prototypeCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Data for the cell, e.g. text for label
id myData = [self myDataForIndexPath:indexPath];
// Prototype knows how to calculate its height for the given data
return [self.prototypeCell myHeightForData:myData];
}
Of course, if you're using custom height, you probably have multiple cell prototypes, so you'd store them in a dictionary or something.
As far as I can tell, the table view doesn't attempt to reuse the prototype, presumably because it was dequeued outside of cellForRowAtIndexPath:. This approach has worked very well for us because it allows the designer to modify cells layouts in the storyboard without requiring any code changes.
Edit: clarified the meaning of sample code and added an example for the case of static height.
I created a category for UITableView some time ago that may come helpful for this. It stores 'prototype' cells using asociated objects for reusing the prototypes and provides a convenience method for obtaining the height of the row assigned in storyboard. The prototypes are released when the table view is deallocated.
UITableView+PrototypeCells.h
#import <UIKit/UIKit.h>
#interface UITableView (PrototypeCells)
- (CGFloat)heightForRowWithReuseIdentifier:(NSString*)reuseIdentifier;
- (UITableViewCell*)prototypeCellWithReuseIdentifier:(NSString*)reuseIdentifier;
#end
UITableView+PrototypeCells.m
#import "UITableView+PrototypeCells.h"
#import <objc/runtime.h>
static char const * const key = "prototypeCells";
#implementation UITableView (PrototypeCells)
- (void)setPrototypeCells:(NSMutableDictionary *)prototypeCells {
objc_setAssociatedObject(self, key, prototypeCells, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSMutableDictionary *)prototypeCells {
return objc_getAssociatedObject(self, key);
}
- (CGFloat)heightForRowWithReuseIdentifier:(NSString*)reuseIdentifier {
return [self prototypeCellWithReuseIdentifier:reuseIdentifier].frame.size.height;
}
- (UITableViewCell*)prototypeCellWithReuseIdentifier:(NSString*)reuseIdentifier {
if (self.prototypeCells == nil) {
self.prototypeCells = [[NSMutableDictionary alloc] init];
}
UITableViewCell* cell = self.prototypeCells[reuseIdentifier];
if (cell == nil) {
cell = [self dequeueReusableCellWithIdentifier:reuseIdentifier];
self.prototypeCells[reuseIdentifier] = cell;
}
return cell;
}
#end
Usage
Obtaining the static height set in storyboard is as simple as this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [tableView heightForRowWithReuseIdentifier:#"cellIdentifier"];
}
Assuming a multi-section table view:
enum {
kFirstSection = 0,
kSecondSection
};
static NSString* const kFirstSectionRowId = #"section1Id";
static NSString* const kSecondSectionRowId = #"section2Id";
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height = tableView.rowHeight; // Default UITableView row height
switch (indexPath.section) {
case kFirstSection:
height = [tableView heightForRowWithReuseIdentifier:kFirstSectionRowId];
break;
case kSecondSection:
height = [tableView heightForRowWithReuseIdentifier:kSecondSectionRowId];
}
return height;
}
And finally if the row height is dynamic:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
id thisRowData = self.allData[indexPath.row]; // Obtain the data for this row
// Obtain the prototype cell
MyTableViewCell* cell = (MyTableViewCell*)[self prototypeCellWithReuseIdentifier:#"cellIdentifier"];
// Ask the prototype cell for its own height when showing the specified data
return [cell heightForData:thisRowData];
}