Load more cells when user scrolls to bottom of tableview frame - ios

I am adding new data to a tableView when the user scrolls to bottom like this....
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
float height = self.itemListTableView.frame.size.height;
float contentYoffset = aScrollView.contentOffset.y;
float distanceFromBottom = aScrollView.contentSize.height - contentYoffset;
if (distanceFromBottom < height) {
NSLog(#" you reached end of the table");
isTableScrolledToBottom = YES;
}else{
isTableScrolledToBottom = NO;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"CellIdentifier";
ListViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell==nil){
cell = [[ListViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Assign values to labels here..
if (isTableScrolledToBottom) {
if (indexPath.row == [apiMutableArray count]-1)
{
// Call the API
NSLog(#"Call the api");
int pgn = [self.currentPageNumber intValue];
pgn = pgn+1;
self.currentPageNumber = [NSNumber numberWithInt:pgn];
NSLog(#"incremented page number : %#",self.currentPageNumber);
NSMutableDictionary *mutableDict = [self.poPostDict mutableCopy];
[mutableDict setObject:self.currentPageNumber forKey:#"pageno"];
self.poPostDict = [mutableDict mutableCopy];
[self addLoaderViewAtBottom];
[self loadPOData:self.poPostDict];
}
}
return cell;
}
Now if the data in table view is big enough, the user will scroll to bottom and data fetching is done from api as expected.However when I filter this table and lets say I get only one row of data.
In such case even if I can see only one cell is present in the table, if I try to scroll the table, the api request is hit.
How to actually know if user has scrolled beyond its frame and upto scrollView's end so that only in that case I can fetch the data.
Note:- Using current technique if only one row is present and I try to scroll, the api call is hit continuously without stopping.

try this solution, it always work for me.
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let lastElement = dataSource.count - 1
if indexPath.row == lastElement {
// logic here
}
}
If you still have doubt then ask.

Related

Scroll to the last two rows of table

when the table has more records than the screen let you see, and the user scroll to the bottom for the last records in the table, the last two rows are not accessible (just in iOS7, in iOS8 everything is fine). Once the user take the finger from screen, the table goes up, and the last two rows are hidden.
This is one of the method used for tableView but i dont know if this come from here:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if (selectionCategMsg == 0)
{
if ([self.tableauMsgReceived count] < 1) {
cell.textLabel.text = NSLocalizedString(#"noMessagesYet", nil); //#"No messages yet !";
}
else
{
cell.textLabel.text = [NSString stringWithFormat:#"%#", [self.tableauMsgReceived objectAtIndex:indexPath.row]];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
}
else
{
if ([self.tableauMsgSent count] < 1) {
cell.textLabel.text = NSLocalizedString(#"noReplyYet", nil);
}
else
{
cell.textLabel.text = [NSString stringWithUTF8String:[NSString stringWithFormat:#"%#", [self.tableauMsgSent objectAtIndex:indexPath.row]].UTF8String];
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;
}
}
[cell setAccessoryType:UITableViewCellAccessoryNone];
return cell;
}
Can someone give an advice ? Thank you.
THIS IS THE SOLUTION THAT I FOUND:
// -> SET TABLE FRAME
CGFloat sideMargin = 0;
CGFloat originX = sideMargin;
CGFloat topBottomMargin = 100;
CGFloat originY = topBottomMargin;
// Width based on view size
CGFloat sizeWidth = self.view.bounds.size.width);
// Height based on view size
CGFloat sizeHeight = (self.view.bounds.size.height - topBottomMargin);
self.myTableView.frame = CGRectMake(originX, originY, sizeWidth, sizeHeight);
// <- END SET tableView frame
UITableView has the method scrollToRowAtIndexPath:atScrollPosition:animated:. That method lets you scroll the table view so a given indexPath is visible. Is that what you're looking for?
EDIT:
#Daij-Djan's post is a good theory on what's wrong. If your table view goes off the screen then it will position the last couple of cells inside it's view, but that view won't be visible. You're going to need to do some debugging to figure out what's wrong. You might try setting the table view's layer's borderWidth to a non-zero value so you can see the bounds of the table view.
In your viewDidLoad, add code like this:
myTableView.layer.borderWidth = 1;
myTableView.layer.borderColor= [UIColor redColor].CGColor;
(Where you'd replace "myTableView" with the name of your table view instance variable or property.)
You'll have to import QuartzCore.h in order for the code above to compile:
#import <QuartzCore/QuartzCore.h>

UILabel upon a few UITableViewCells

Am I able to create a UILabel that is layouted upon many UITableViewCells?
I'm trying to make something like (that is just one section of my UITableView, each section can have one or more rows):
---------------------------------------------
| Multi-lined label | row1 values |
| with some useless | row2 values |
| text | row3 values |
---------------------------------------------
I managed to create a UILabel (in the first row of a section) that is multi-lined and is not clipping to bounds. That works really well (it was a bit tricky to count each sections row heights, but doable) besides one case: when I'm scrolling UITableView from bottom to top - UITableView renders last row (without UILabel) so it has "no evidence" of having UILabel (because it is maintained in the first row of section). Can I force some kind of relayouting first cell in section? I tried reloadRowsAtIndexPaths:withRowAnimation: with first row in each section whenever I layouted not first cell in section but it gave me layouting errors that I really do not understand. Or maybe there is another idea to do so?
-- EDITED
To be clear: I have a custom UITableViewCell with an IB view, it has a few labels that each row consist of and a label named labelName that I want to be "multi-lined" along rows in that section. LabelName.text is empty for each row besides first one in each section.
I am adding somescreenshots:
Good screenshot - when I am scrolling to bottom I'm getting proper effect:
Bad screenshot - when I am scrolling up, UITableView renders last row of section firstly, and afterwards renders upper rows - that gives effect of cut label (because multi-line label is in the first row)
I am not sure if code here will add anything to question - it is rather simple and almost whole logic is in tableView:heightForRowAtIndexPath. I can only present how do I create custom UITableViewCell:
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
cell.contentView.superview.clipsToBounds = NO;
}
-- EDIT 2
Here is most of the code:
- (void) reloadData
{
NSUInteger index = 0;
for (NSDictionary *object in self.list) {
CGFloat height = [[object objectForKey:#"name"] sizeWithFont:self.labelFont constrainedToSize:self.labelSize].height;
[self.labelHeights addObject:NSNumberFloat(ceilf(height))];
index++;
}
[self.tableView reloadData];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *object = [self.list objectAtIndex:indexPath.section];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
NSUInteger count = [[object objectForKey:#"list"] count];
CGFloat cellHeight = 30.f;
if((indexPath.row + 1) == count){
cellHeight = MAX(8.f + height - 30.f * indexPath.row, 30.f);
}
return cellHeight;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.list count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.list objectAtIndex:section] objectForKey:#"list"] count];
}
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *person = [self.list objectAtIndex:indexPath.section];
NSDictionary *object = [[person objectForKey:#"list"] objectAtIndex:indexPath.row];
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:[CustomTableViewCell reuseIdentifier]];
if (cell == nil) {
cell = [[CustomTableViewCell alloc] initWithOwner:self];
cell.backgroundColor = [UIColor clearColor];
cell.userInteractionEnabled = NO;
cell.clipsToBounds = NO;
cell.labelName.clipsToBounds = NO;
[cell.contentView.superview setClipsToBounds:NO];
}
if(indexPath.row == 0){
cell.labelName.text = [person objectForKey:#"name"];
CGFloat height = [[self.labelHeights objectAtIndex:indexPath.section] floatValue];
cell.labelName.numberOfLines = (int)(height / self.fontSizeHeight);
cell.labelName.frame = CGRectChangeHeight(cell.labelName.frame, height);
}
else{
cell.labelName.text = #"";
}
CGFloat cellHeight = [self tableView:self.tableView heightForRowAtIndexPath:indexPath];
cell.borderTop.hidden = YES;
cell.borderBottom.hidden = YES;
cell.borderBottomSmall.hidden = NO;
if(indexPath.row == 0){
cell.borderTop.hidden = NO;
}
if(indexPath.row + 1 == [[person objectForKey:#"list"] count]){
cell.borderBottom.hidden = NO;
cell.borderBottom.frame = CGRectChangeY(cell.borderBottom.frame, cellHeight - 1.f);
cell.borderBottomSmall.hidden = YES;
}
cell.labelDate.text = [object objectForKey:#"date"];
cell.labelPremium.text = [[object objectForKey:#"premium"];
return cell;
}
-- PARTIAL ANSWER
I managed to create a hack, that makes multi-line UILabel visibile when scrolling bottom to up at some point:
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
NSArray *cells = [self.tableView visibleCells];
UITableViewCell *cell = [cells objectAtIndex:0];
[cell.superview bringSubviewToFront:cell];
}
I noticed that the part of the UILabel is covered by a row thats below of the UILabels row and that hack makes it would be properly displayed. But it has a drawback, when scrolling slowly from bottom to top it generates a flicker when label is created (part of it should be visible before real creation of UILabel).
Up mentioned answers are not solutions, but "hacks".
In the cell == nil block should be only the initialization.
You should not add any subviews in cell in cellForRowAtIndexPath.
The reason is simple: I will reuse a cell with some labels already added and add a new label.
Either use the default cell.textLabel, either create a subclass for UITableViewCell, with a
-(void)setData:(dictionary or string)object;
and in implementation just set the proper data to proper UI controls.
Add/create controls either in init method in the subclass, or in IB/Storyboard.
Call the dictionary or string should be picked in correspondence to indexPath, so you will always get proper data for proper cell at proper indexPath.
Try This
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = #"cellId";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (cell==nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
}
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
/// your UI on cell goes here
return cell;
}

Change heightForRowAtIndexPath depending on type of UITableViewCell it uses?

In cellForRowAtIndexPath, I'm using randomisation to create one of two different custom UITableViewCell types, let's call them LCImageCell and LCTextCell (one contains an image, one contains some text, and it's random which will be displayed in each row). This is laid out very basically as:
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
// Determine whether the cell should contain an image or text..
BOOL isCellAnImage;
int randomChanceOfImageAppearing = arc4random() % 5;
if (randomChanceOfImageAppearing == 4) isCellAnImage = YES;
else isCellAnImage = NO;
// If the cell is going to contain an image..
if (isCellAnImage) {
LCIImageCell *imageCell = [tableView dequeueReusableCellWithIdentifier: #"ImageCell"];
if (imageCell == nil) {
imageCell = [[LCImageCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: #"ImageCell"];
}
return imageCell;
// Else the cell will contain text..
} else {
// Make and allocate the cell if necessary.
LCTextCell *customCell = [tableView dequeueReusableCellWithIdentifier: #"CustomCell"];
if (customCell == nil) {
customCell = [[LCTextCell alloc] initWithStyle: UITableViewCellStyleValue1 reuseIdentifier: #"CustomCell"];
}
return customCell;
}
}
I need to dynamically set the height of those with text (the LCTextCell instances), and that's working correctly. I'm now coming to integrate the image cells, and I'm wondering how I can let heightForRowAtIndexPath know whether the cell in question is an LCImageCell or a LCTextCell, so that I can only apply that height adjustment if the cell in question is an LCTextCell.
Can I access the cell that the height is being applied to before its height is set? Has it even been created / alloc'd / init'd by that point in time?
The seemingly logical thing is to invoke -cellForRowAtIndexPath: from within -tableView:heightForRowAtIndexPath:. However this is bad form (the latter is called well before the former for a reason) and can lead to performance issues (heightForRowAtIndexPath is called for every cell in the table, even the non-visible ones, each time the table is displayed).
Instead, move the cell randomization to earlier in the viewController's lifecycle. For instance, assuming you know the height for each cell type in advance (and your tableView has a single section):
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
for (int i = 0; i < [self.tableView numberOfRowsInSection:0]; i++) {
if (arc4random() % 5 == 4) {
[mutableArray addObject:[LCImageCell class]];
} else {
[mutableArray addObject:[LCImageCell class]];
}
}
self.cellTypes = [NSArray arrayWithArray:mutableArray];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([[self.cellTypes objectAtIndex:indexPath.row] isEqual:[LCImageCell class]]) {
[LCImageCell height]; // class method returns static height for an image cell
} else {
[LCTextCell height]; // class method returns static height for a text cell
};
}
If the height is dynamic, you should to calculate and store that in advance as well.
Create a class (say A) for LCTextCell and another class (say B) for LCImageCell. You can define the classes to hold cell specific information (You can define a NSString instance inside class A which can be the string that gets displayed inside the LCTextCell and similarly you can have a url instance inside class B that would represent an image that is displayed inside LCImageCell). You will have to hold an array (say myArray) in your viewcontroller that would contain instances of A or B depending on what cell you would want on the specific index (row of tableview). You just have to make sure that the array has proper entries before heightForRowAtIndexPath method gets called.
You can then have
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([[self.myArray objectAtIndex:row] isKindOfClass:[A class]])
{
//LCTextCell
return ...;
}
else if([[self.myArray objectAtIndex:row] isKindOfClass:[B class]])
{
//LCImageCell
return ...;
}
return 50.0f;
}
You can do something like this:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *customCell = [self.tableView cellForRowAtIndexPath:indexPath];
if([customCell isMemberOfClass:[LCImageCell class])
{
return ...;
}
else if([customCell isMemberOfClass:[LCTextCell class])
{
return ...;
}
return 0.0f;
}

Making a UITableView scroll like a UIPickerView

I have a UITableView on a UIViewController.
And this table has "say" 5 visible categories to choose from. But in the Array that holds the content i have "say" 10 categories. What i've created now is a normal scrolling table that goes from category 1 on index 0 to category 10 on index 9.
But what i'd rather have is category 1 also after category 10 and the other way around.
So basically an endless loop of a static Array.
I've tryed this with the scrollViewDidScroll: method, but when i do that it doesn't scroll like you would expect a UITableView to scroll. It RACES to a random spot and moving 1 or two categories is impossible.
Here is a sample of my code. Hope somebody can help.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == categoryView){
if (categoryView.contentOffset.y > 0.0) {
loopcounter++;
NSLog(#"ScrollView Scrolled DOWN");
[categorytablecontent insertObject:[categorytablecontent objectAtIndex:0] atIndex:[categorytablecontent count]-1];
[categorytablecontent removeObjectAtIndex:0];
[categoryView reloadData];
}
if (categoryView.contentOffset.y < 0.0) {
loopcounter = [categorytablecontent count];
NSLog(#"ScrollView Scrolled UP");
[categorytablecontent insertObject:[categorytablecontent objectAtIndex:[categorytablecontent count]-1] atIndex:0];
[categorytablecontent removeObjectAtIndex:[categorytablecontent count]-1];
[categoryView reloadData];
}
a = categoryView.visibleCells;
for (UITableViewCell *cell in a){
NSLog(#"Current Visible cell: %#", cell.textLabel.text);
}
NSLog(#"Current offset: %#", categoryView.contentOffset.y);
}
}
There is more then one good way to do this. Does your controller implement the UITableViewDataSource Protocol? If so, the easiest thing would probably be to just return the modulo of your dataset size in cellForRowAtIndexPath.
Something like:
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
// Use appropriate fields for your categorytablecontent object here...
cell.textLabel.text = [categorytablecontent objectAtIndex:(indexPath.row % [categorytablecontent count])];
return cell;
}
This should result in a nice smooth infinite scroll that loops through your static (or dynamic) content.

Can you animate a height change on a UITableViewCell when selected?

I'm using a UITableView in my iPhone app, and I have a list of people that belong to a group. I would like it so that when the user clicks on a particular person (thus selecting the cell), the cell grows in height to display several UI controls for editing the properties of that person.
Is this possible?
I found a REALLY SIMPLE solution to this as a side-effect to a UITableView I was working on.....
Store the cell height in a variable that reports the original height normally via the tableView: heightForRowAtIndexPath:, then when you want to animate a height change, simply change the value of the variable and call this...
[tableView beginUpdates];
[tableView endUpdates];
You will find it doesn't do a full reload but is enough for the UITableView to know it has to redraw the cells, grabbing the new height value for the cell.... and guess what? It ANIMATES the change for you. Sweet.
I have a more detailed explanation and full code samples on my blog... Animate UITableView Cell Height Change
I like the answer by Simon Lee. I didn't actually try that method but it looks like it would change the size of all the cells in the list. I was hoping for a change of just the cell that is tapped. I kinda did it like Simon but with just a little difference. This will change the look of a cell when it is selected. And it does animate. Just another way to do it.
Create an int to hold a value for the current selected cell index:
int currentSelection;
Then:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
int row = [indexPath row];
selectedNumber = row;
[tableView beginUpdates];
[tableView endUpdates];
}
Then:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath row] == currentSelection) {
return 80;
}
else return 40;
}
I am sure you can make similar changes in tableView:cellForRowAtIndexPath: to change the type of cell or even load a xib file for the cell.
Like this, the currentSelection will start at 0. You would need to make adjustments if you didn't want the first cell of the list (at index 0) to look selected by default.
Add a property to keep track of the selected cell
#property (nonatomic) int currentSelection;
Set it to a sentinel value in (for example) viewDidLoad, to make sure that the UITableView starts in the 'normal' position
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//sentinel
self.currentSelection = -1;
}
In heightForRowAtIndexPath you can set the height you want for the selected cell
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
int rowHeight;
if ([indexPath row] == self.currentSelection) {
rowHeight = self.newCellHeight;
} else rowHeight = 57.0f;
return rowHeight;
}
In didSelectRowAtIndexPath you save the current selection and save a dynamic height, if required
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// do things with your cell here
// set selection
self.currentSelection = indexPath.row;
// save height for full text label
self.newCellHeight = cell.titleLbl.frame.size.height + cell.descriptionLbl.frame.size.height + 10;
// animate
[tableView beginUpdates];
[tableView endUpdates];
}
}
In didDeselectRowAtIndexPath set the selection index back to the sentinel value and animate the cell back to normal form
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
// do things with your cell here
// sentinel
self.currentSelection = -1;
// animate
[tableView beginUpdates];
[tableView endUpdates];
}
}
Instead of beginUpdates()/endUpdates(), the recommended call is now:
tableView.performBatchUpdates(nil, completion: nil)
Apple says, regarding beginUpdates/endUpdates: "Use the performBatchUpdates(_:completion:) method instead of this one whenever possible."
See: https://developer.apple.com/documentation/uikit/uitableview/1614908-beginupdates
reloadData is no good because there's no animation...
This is what I'm currently trying:
NSArray* paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[self.tableView endUpdates];
It almost works right. Almost. I'm increasing the height of the cell, and sometimes there's a little "hiccup" in the table view as the cell is replaced, as if some scrolling position in the table view is being preserved, the new cell (which is the first cell in the table) ends up with its offset too high, and the scrollview bounces to reposition it.
I don't know what all this stuff about calling beginUpdates/endUpdates in succession is, you can just use -[UITableView reloadRowsAtIndexPaths:withAnimation:]. Here is an example project.
I resolved with reloadRowsAtIndexPaths.
I save in didSelectRowAtIndexPath the indexPath of cell selected and call reloadRowsAtIndexPaths at the end (you can send NSMutableArray for list of element's you want reload).
In heightForRowAtIndexPath you can check if indexPath is in the list or not of expandIndexPath cell's and send height.
You can check this basic example:
https://github.com/ferminhg/iOS-Examples/tree/master/iOS-UITableView-Cell-Height-Change/celdascambiadetam
It's a simple solution.
i add a sort of code if help you
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath
{
if ([indexPath isEqual:_expandIndexPath])
return 80;
return 40;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Celda";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
[cell.textLabel setText:#"wopwop"];
return cell;
}
#pragma mark -
#pragma mark Tableview Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSMutableArray *modifiedRows = [NSMutableArray array];
// Deselect cell
[tableView deselectRowAtIndexPath:indexPath animated:TRUE];
_expandIndexPath = indexPath;
[modifiedRows addObject:indexPath];
// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
}
Swift 4 and Above
add below code into you tableview's didselect row delegate method
tableView.beginUpdates()
tableView.setNeedsLayout()
tableView.endUpdates()
Try this is for expanding indexwise row:
#property (nonatomic) NSIndexPath *expandIndexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if ([indexPath isEqual:self.expandedIndexPath])
return 100;
return 44;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *modifiedRows = [NSMutableArray array];
if ([indexPath isEqual:self.expandIndexPath]) {
[modifiedRows addObject:self.expandIndexPath];
self.expandIndexPath = nil;
} else {
if (self.expandedIndexPath)
[modifiedRows addObject:self.expandIndexPath];
self.expandIndexPath = indexPath;
[modifiedRows addObject:indexPath];
}
// This will animate updating the row sizes
[tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
// Preserve the deselection animation (if desired)
[tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ViewControllerCellReuseIdentifier];
cell.textLabel.text = [NSString stringWithFormat:#"I'm cell %ld:%ld", (long)indexPath.section, (long)indexPath.row];
return cell;
}
BOOL flag;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
flag = !flag;
[tableView beginUpdates];
[tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return YES == flag ? 20 : 40;
}
just a note for someone like me searching for add "More Details" on custom cell.
[tableView beginUpdates];
[tableView endUpdates];
Did a excellent work, but don't forget to "crop" cell view.
From Interface Builder select your Cell -> Content View -> from Property Inspector select "Clip subview"
Heres a shorter version of Simons answer for Swift 3. Also allows for toggling of the cell's selection
var cellIsSelected: IndexPath?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
cellIsSelected = cellIsSelected == indexPath ? nil : indexPath
tableView.beginUpdates()
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if cellIsSelected == indexPath {
return 250
}
return 65
}
Swift Version of Simon Lee's answer .
// MARK: - Variables
var isCcBccSelected = false // To toggle Bcc.
// MARK: UITableViewDelegate
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// Hide the Bcc Text Field , until CC gets focused in didSelectRowAtIndexPath()
if self.cellTypes[indexPath.row] == CellType.Bcc {
if (isCcBccSelected) {
return 44
} else {
return 0
}
}
return 44.0
}
Then in didSelectRowAtIndexPath()
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
// To Get the Focus of CC, so that we can expand Bcc
if self.cellTypes[indexPath.row] == CellType.Cc {
if let cell = tableView.cellForRowAtIndexPath(indexPath) as? RecipientTableViewCell {
if cell.tag == 1 {
cell.recipientTypeLabel.text = "Cc:"
cell.recipientTextField.userInteractionEnabled = true
cell.recipientTextField.becomeFirstResponder()
isCcBccSelected = true
tableView.beginUpdates()
tableView.endUpdates()
}
}
}
}
Yes It's Possible.
UITableView has a delegate method didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[UIView animateWithDuration:.6
delay:0
usingSpringWithDamping:UIViewAnimationOptionBeginFromCurrentState
initialSpringVelocity:0
options:UIViewAnimationOptionBeginFromCurrentState animations:^{
cellindex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
[violatedTableView beginUpdates];
[violatedTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
[violatedTableView endUpdates];
}
completion:^(BOOL finished) {
}];
}
But in your case if the user scrolls and selects a different cell then u need to have the last selected cell to shrink and expand the currently selected cell reloadRowsAtIndexPaths: calls heightForRowAtIndexPath: so handle accordingly.
Here is my code of custom UITableView subclass, which expand UITextView at the table cell, without reloading (and lost keyboard focus):
- (void)textViewDidChange:(UITextView *)textView {
CGFloat textHeight = [textView sizeThatFits:CGSizeMake(self.width, MAXFLOAT)].height;
// Check, if text height changed
if (self.previousTextHeight != textHeight && self.previousTextHeight > 0) {
[self beginUpdates];
// Calculate difference in height
CGFloat difference = textHeight - self.previousTextHeight;
// Update currently editing cell's height
CGRect editingCellFrame = self.editingCell.frame;
editingCellFrame.size.height += difference;
self.editingCell.frame = editingCellFrame;
// Update UITableView contentSize
self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height + difference);
// Scroll to bottom if cell is at the end of the table
if (self.editingNoteInEndOfTable) {
self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y + difference);
} else {
// Update all next to editing cells
NSInteger editingCellIndex = [self.visibleCells indexOfObject:self.editingCell];
for (NSInteger i = editingCellIndex; i < self.visibleCells.count; i++) {
UITableViewCell *cell = self.visibleCells[i];
CGRect cellFrame = cell.frame;
cellFrame.origin.y += difference;
cell.frame = cellFrame;
}
}
[self endUpdates];
}
self.previousTextHeight = textHeight;
}
I used #Joy's awesome answer, and it worked perfectly with ios 8.4 and XCode 7.1.1.
In case you are looking to make your cell toggle-able, I changed the -tableViewDidSelect to the following:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//This is the bit I changed, so that if tapped once on the cell,
//cell is expanded. If tapped again on the same cell,
//cell is collapsed.
if (self.currentSelection==indexPath.row) {
self.currentSelection = -1;
}else{
self.currentSelection = indexPath.row;
}
// animate
[tableView beginUpdates];
[tableView endUpdates];
}
I hope any of this helped you.
Check this method after iOS 7 and later.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
Improvements have been made to this in iOS 8. We can set it as property of the table view itself.
Swift version of Simon Lee's answer:
tableView.beginUpdates()
tableView.endUpdates()
Keep in mind that you should modify the height properties BEFORE endUpdates().
Inputs -
tableView.beginUpdates()
tableView.endUpdates()
these functions will not call
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
But, if you do,
tableView.reloadRows(at: [selectedIndexPath! as IndexPath], with: .none)
It will call the
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
this function.
I just resolved this problem with a little hack:
static int s_CellHeight = 30;
static int s_CellHeightEditing = 60;
- (void)onTimer {
cellHeight++;
[tableView reloadData];
if (cellHeight < s_CellHeightEditing)
heightAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:#selector(onTimer) userInfo:nil repeats:NO] retain];
}
- (CGFloat)tableView:(UITableView *)_tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (isInEdit) {
return cellHeight;
}
cellHeight = s_CellHeight;
return s_CellHeight;
}
When I need to expand the cell height I set isInEdit = YES and call the method [self onTimer] and it animates the cell growth until it reach the s_CellHeightEditing value :-)
Get the indexpath of the row selected. Reload the table. In the heightForRowAtIndexPath method of UITableViewDelegate, set the height of the row selected to a different height and for the others return the normal row height

Resources