This question already has answers here:
Change UITableView height dynamically
(8 answers)
Closed 9 years ago.
Please help me to ask this question. I tried all the choices that I could find here.
The sample of my code is attached (I know, it's pretty bad).
How can I change UITableView height dynamically?
- (void)viewDidLoad
{
[super viewDidLoad];
[self vozvratmassiva];
}
- (NSMutableArray *) vozvratmassiva
{
...
return array;
}
-(NSInteger)numberOfSectionInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *a = [self vozvratmassiva];
return a.count;
}
-(UITableViewCell *)tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
return commonsize;
}
#end
I understand what you're trying to do. Here is what you should do:
Add height constraint to your UITableView
Wrap it in custom UIView
Make a custom class MyCustomView:UIView
Set class in IB for your wraper UIView to your class from step 3.
Make connection from constraint in IB to your class
Make a connection between table view and your class
Put code into your new class:
- (void) layoutSubviews {
// set height 0, will calculate it later
self.constraint.constant = 0;
// force a table to redraw
[self.tableView setNeedsLayout];
[self.tableView layoutIfNeeded];
// now table has real height
self.constraint.constant = self.tableView.contentSize.height;
}
To change the cell height:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return commonsize;
}
To change tableView height:
tableView.size.height = 100;
Related
I have to make the height of UITableViewCell dynamic with respect to number of images in the cell.
Add following lines of code in your viewDidLoad():
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableViewAutomaticDimension
where estimatedRowHeight is the maximum height you want to set for UITableViewCell.
Also, add this delegate method and return UITableViewAutomaticDimension:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
set the UITableView's rowHight property to UITableViewAutomaticDimension and set the estimated row height to what the most common height is. Then in your layout of the cell make sure there are constraints between all of the visual elements vertically and the cell will be laid out properly. The vertical constraints should be something like:
V:|-(5)-[imageOne]-[imageTwo]-|
Where "|" is the cell's content view.
Implement the delegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath (NSIndexPath *)indexPath
#interface yourController : UIViewController<UITableViewDelegate,UITableViewDataSource>
set delegate in view did load
- (void)viewDidLoad {
[super viewDidLoad];
tableview.delegate=self;
tableview.dataSource=self;
}
now method of height of table view cell
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 40;//your value
}
in cell
- (CGFloat)cellHeight {
[self layoutIfNeeded];
return CGRectGetMaxY(_imageview.frame) + 10;
}
the imageView is the last view in cell. It only needs maxY of the lastView
in controller
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
OJTopicRootCell *cell = (OJTopicRootCell *)[self tableView:tableView cellForRowAtIndexPath:indexPath];
return cell.cellHeight;
}
Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
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.
Here's a video of what's going on: https://imgflip.com/gif/kgvcq
Basically, if the cells scroll past the bottom edge of the screen, it won't bounce back. I've tried updating the contentSize of the tableView but that doesn't seem to be the issue. I've also made sure to declare the rowHeight and still no luck. Lastly, I've made sure the bounce properties of the tableView are set properly.
Sorry for not putting up code, here it is:
// data source
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"frame height: %f", tableView.frame.size.height);
NSLog(#"content size height: %f", tableView.contentSize.height);
static NSString *CellIdentifier = #"HabitCell";
HabitTableViewCell *cell = (HabitTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.viewController = self;
cell.delegate = self;
// edit cell
return cell;
}
The NSLogs are returning: 568 and 400 respectively. Would it be the frame causing problems? Also, I have not overridden scrollViewDidScroll.
Implemented Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [self.habits count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath isEqual:_expandIndexPath]) {
return 450 + heightToAdd;
}
return 100;
}
Fixed: I had a call to scrollToRowAtIndexPath in my UIPanGestureRecognizer method. Removed it and it now works perfectly.
For my UITableView I have such code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 4;
}
But when I launch me app, it shows an extra separator. It's not additional cell - it cannot be selected. How can it be fixed?
Eliminate extra separators below UITableView
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
return [UIView new];
// If you are not using ARC:
// return [[UIView new] autorelease];
}
With iOS7
- (void)viewDidLoad
{
[super viewDidLoad];
self.tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];
self.tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
}
It is by default in UITableView. You can use templates to create the kind of view you need, in an empty view controller (or) change the frame size of your UITableView in code by creating and allocating your own view.
You can also try to set footer view height to zero, by using next code in UITableViewDelegate class:
- (CGFloat) tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
return 0;
}
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];
}