Auto adjust the UITableViewCell height depending on its contents - iphone

I want to adjust the height of a cell depending on its contents. I know UITableViewDelegate lets you implement the
- (CGFloat) tableView: (UITableView *) tableView
heightForRowAtIndexPath: (NSIndexPath *) indexPath {
return someHeight;
}
but I do not want to hardcode the height. Is there a way to do this dynamically?

You have to enter some code into that method that calculates the height of the row content. Exactly what code you need to put depends entirely on what kind of content you're displaying.
For example, if you're displaying text content that may wrap across multiple lines, you're probably going to end up using one of NSString's sizeWithFont: family of methods.

If you want your rows to have different heights, you have to calculate every row's height.
I've met such a problem. I calculated the heights of rows according to contents parsed from a json string. here's what i did.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// parse json
id qWeiboContent = [self.array objectAtIndex:indexPath.row];
float totalContentHeight;
QWeiboContentModel *model = [self getQWeiboContentFromJSON:qWeiboContent];
QWeiboContentModel *subModel = nil;
totalContentHeight += model.forOrComment.heightValue; // comment text view's height
totalContentHeight += model.content.heightValue; // content text view's height
totalContentHeight += 21 * 2; // 21 is height of a label
totalContentHeight += model.imageUrl.heightValue;
totalContentHeight += CELL_CONTENT_MARGIN;
if ([model.type isEqualToString:REPOSTED]) {
id qWeiboSource = [qWeiboContent objectForKey:#"source"];
subModel = [self getQWeiboContentFromJSON:qWeiboSource];
model.source = subModel;
totalContentHeight += subModel.forOrComment.heightValue;
totalContentHeight += subModel.content.heightValue;
totalContentHeight += 21 * 2;
totalContentHeight += subModel.imageUrl.heightValue;
totalContentHeight += CELL_CONTENT_MARGIN;
}
if (self.arrayQQWeibo == nil) {
self.arrayQQWeibo = [[NSMutableArray alloc]init];
}
[self.arrayQQWeibo addObject:model];
return totalContentHeight;
}

Related

Create infinite scroll effect by inserting new rows [duplicate]

How do I do an infinite scrolling in a UITableView? I know how to do it using a UIScrollView, in which apple has demonstrated in one of the WWDC's video. I tried doing the following in tableView:cellForRowAtIndexPath::
if (indexPath.row == [self.newsFeedData_ count] - 1)
{
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
but this fails. Any other idea?
If you need to know when you hit the bottom of the UITableView, become it's delegate (because it is a subclass of UIScrollView), and use the -scrollViewDidScroll: delegate method to compare the table's content height and it's actual scroll position.
EDIT (something like this):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat actualPosition = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height - (someArbitraryNumber);
if (actualPosition >= contentHeight) {
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
}
You can support infinite scroll with pull to refresh at the top and/or scroll continuously at the bottom with a spinner wheel using:
https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh handles the logic when UITableView reaches the bottom. A spinner is shown automatically and a callback block is fired. You add in your business logic to the callback block.
Here's an example:
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
This project can be added to your project using CocoaPods or directly compiled into your project.
Here's a very quick and complete demo of an infinite scrolling UITableView I put together...
#interface InfiniteScrollViewController ()
#property (nonatomic) NSMutableArray *tableViewData;
#property (nonatomic) BOOL loadingMoreTableViewData;
#end
#implementation InfiniteScrollViewController
- (void)viewDidLoad {
self.tableViewData = [[NSMutableArray alloc] init];
[self addSomeMoreEntriesToTableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableViewData.count + 1;
}
- (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];
}
if (indexPath.row < self.tableViewData.count) {
cell.textLabel.text = [self.tableViewData objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = #"Loading more data...";
// User has scrolled to the bottom of the list of available data so simulate loading some more if we aren't already
if (!self.loadingMoreTableViewData) {
self.loadingMoreTableViewData = YES;
[self performSelector:#selector(addSomeMoreEntriesToTableView) withObject:nil afterDelay:5.0f];
}
}
return cell;
}
- (void)addSomeMoreEntriesToTableView {
int loopTill = self.tableViewData.count + 20;
while (self.tableViewData.count < loopTill) {
[self.tableViewData addObject:[NSString stringWithFormat:#"%i", self.tableViewData.count]];
};
self.loadingMoreTableViewData = NO;
[self.tableView reloadData];
}
#end
'UITableView' is same as 'UIScrollView' in 'scrollViewDidScroll' method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualising joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit the method when data count is say for example less than 9.
For me worked better scrollViewDidEndDragging: than scrollViewDidScroll:.
The second approach will send you each position during scroll and cause, if you are fetching remote resources you will hit your endpoint several times, which is not good.
Complete example based on #codafi solution with comments from #danielgomezrico about how to calculate contentHeight:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
CGFloat actualPosition = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height - (self.tableView.frame.size.height);
if (actualPosition >= contentHeight) {
// fetch resources
[self.tableView reloadData];
}
}
Generally I override scrollViewDidEndDecelerating and inside it I put my code to request more data.
Example:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height){
//put here your code
}
}
Recently I uploaded on GitHub a subclass of UITableView, that implements the infinite scroll.
You can download it here:
https://github.com/alchimya/iOS-LazyTableView
rather than overriding we can do this optimally in layoutSubviews.
Here's how I got it implemented. You can get to know more about the implementation here
- (void)layoutSubviews{
[super layoutSubviews];
if(self.delegateForViews){
CGPoint contentOffset = self.contentOffset;
if([self.delegateForViews noOfViews]>numOfReusableViews){
NSUInteger centerIndex=visibleViews.count/2;
NSUInteger noOfViews=[self.delegateForViews noOfViews];
UIView *centerView=[visibleViews objectAtIndex:centerIndex];
CGPoint centerViewOrigin=centerView.frame.origin;
CGSize centerViewSize=centerView.frame.size;
CGFloat offsetDifference=contentOffset.x-centerViewOrigin.x;
CGFloat offsetDifferenceAbs=fabs(contentOffset.x-centerViewOrigin.x);
if(offsetDifferenceAbs>=centerViewSize.width){
if(offsetDifference<0){
currentPosition--;
}else{
currentPosition++;
}
self.contentOffset=centerViewOrigin;
currentPosition=[self getPosition:currentPosition noOfViews:noOfViews];
[self.delegateForViews clearView:centerView];
[self.delegateForViews setupView:centerView forPosition:currentPosition];
for (int i=centerIndex-1; i>=0; i--) {
UIView* prevView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:prevView];
[self.delegateForViews setupView:prevView forPosition:
[self getPosition:currentPosition-1 noOfViews:noOfViews]];
}
for (int i=centerIndex+1; i<visibleViews.count; i++) {
UIView* nextView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:nextView];
[self.delegateForViews setupView:nextView forPosition:
[self getPosition:currentPosition+1 noOfViews:noOfViews]];
}
}
}
}
}
One of the simple and that offered me everything i need is this class:
https://github.com/jakemarsh/JMStatefulTableViewController
You just need to subclass JMStatefulTableViewController and the it has 3 methods that you need to overwrite:
one that is called on init, to get the initial data
statefulTableViewControllerWillBeginInitialLoading
one when the user pull to refresh
statefulTableViewControllerWillBeginLoadingFromPullToRefresh
one when is called for the infinite scroll (next page)
statefulTableViewControllerWillBeginLoadingNextPage
This can be used from Cocoapods too.
scrollviewDidScroll will call when you move through the rows in tableview
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//check for the visible rows
let indexpath = self.tableView.indexPathsForVisibleRows?.last
//check if the visible row last is equal to the total number of counts
if(indexpath?.last == self.listCount){
//code for adding data to the tableview and reload the table view.
}
}
look in the link for more details about indexPathForVisibleRows
https://developer.apple.com/documentation/uikit/uitableview/1614885-indexpathsforvisiblerows

Detect Distance between UITableView Section Headers

In a plain UITableView with custom UIView's as section headers, is there a way to calculate:
When one of the Section is on the top, the distance between that section and the next one that would come?
I am expecting to calculate this here:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
You can find the number of rows in that section by calling the tableView:numberOfRowsInSection: method from the UITableViewDataSourceDelegate protocol. You can get the height for each row in the section with the tableView:heightForRowAtIndexPath: method from the UITableViewDelegate protocol. Add up the height for all the rows and you have the distance you want.
Your code would look something like this, assuming you have a reference to the tableview and the section.
float totalHeight = 0;
for (int i = 0; i < [tableViewDataSourceDelegate
tableView:tableView
numberOfRowsInSection:section]; i ++) {
totalHeight += [tableViewDelegate
tableView:tableView
heightForRowAtIndexPath:[NSIndexPath
indexPathForRow:i
inSection:section]];
}
Haven't had a chance to test this code, but it Should Work[tm].
Edit
This will only work if the header is at the top.
(assuming all rows are same height)
NSIndexPath *topCellIndexPath = [tableView indexPathsForVisibleRows][0];
UITableViewCell *topCell = [tableView cellForRowAtIndexPath: topCellIndexPath];
CGFloat distanceToNextSection = [tableView convertRect: [topCell frame] fromView: topCell.superview].origin.y - tableView.contentOffset.y + ([self tableView: tableView numberOfRowsInSection: topCellIndexPath.section] - topCellIndexPath.row)*tableView.rowHeight
I was able to solve this by doing the following:
Before creating an section header, check if you have the section header for a given section. If you do return it from the NSMutableArray. If not keep going.
When you create the section header, keep a reference to it in a NSMutableArray.
When you scroll in:
-(void)scrollViewDidScroll:(UIScrollView *)scrollView
Do the following:
// Get the toppest section
NSUInteger sectionNumber = [[self.tableView indexPathForCell:[[self.tableView visibleCells] objectAtIndex: 0]] section];
// Get a reference to it
SFBasicSectionHeader *topHeader = [arrayOfWeakHeaders objectAtIndex:sectionNumber];
SFBasicSectionHeader *bellowHeader;
// Check if it's Ok to get the bellow header
if (sectionNumber+1<[arrayOfWeakHeaders count] && [arrayOfWeakHeaders objectAtIndex:sectionNumber +1])
{
bellowHeader = [arrayOfWeakHeaders objectAtIndex:sectionNumber+1];
}
The difference between both will be:
CGFloat differenceBetweenTopAndBellowSection = bellowHeader.frame.origin.y - topHeader.frame.size.height - self.tableView.contentOffset.y;
Done.

Expanded UITableViewCell according to UILabel size

I have a UITableView with 4 UILabel's: Title, Body, Author and Date, he looks like this:
What I want to accomplish is, when user click on the cell itself, another label should be added to the cell, the "Body" label and the cell should expand according to this label size.
Something like this:
How can I do that? I've searched stackoverflow, tried some code pieces, but still didn't found the right solution.
Thanks!
Edit 1: 14.11.12 at 14:52
I managed to change the size of the UILabel with the current text:
- (CGRect )resizeLabelByFontSize:(UILabel *)customCellLabel withMaxHeightSize:(CGFloat )maxHeight
{
CGSize maximumLabelSize = CGSizeMake(239, maxHeight);
CGSize expectedLabelSize = [customCellLabel.text sizeWithFont:customCellLabel.font constrainedToSize:maximumLabelSize lineBreakMode:customCellLabel.lineBreakMode];
//adjust the label the the new height.
CGRect newFrame = customCellLabel.frame;
newFrame.size.height = expectedLabelSize.height;
return newFrame;
}
But how can I change the size of the cell according to the size of the new UILabel?
By seeing Images in Question
Here is the method which just create the Dynamic FRAME for UILabel have a look at this
By getting the Height and Width for UIlabel you can calculate the Whole height and could set the Row Height of UITableView.
- (void)setLabeltextWithVerticalAlignTop:(NSString *)theText
{
CGSize labelSize;
// here labelSize is hard-wired but could use constants to populate the size
labelSize = CGSizeMake(210, 129);//this is just for example
//now create the Size from textString SO that We could assign this size to the Label.
CGSize theStringSize = [theText sizeWithFont:lblTitle.font constrainedToSize:labelSize lineBreakMode:lblTitle.lineBreakMode];
lblTitle.frame = CGRectMake(lblTitle.frame.origin.x, lblTitle.frame.origin.y, theStringSize.width, theStringSize.height);
lblTitle.text = theText;
}
Call Above Method For setting the height and Width of description Label you need to pass the text to be shown on that description label.
As you gets the height for that Label, Now On the Basis of this You can Adjust the heigh of Row of TableView.
EDIT:Above Code Just Create the Dynamic Frame For The UILabel
You should take a view of this this is what you looking for....!!!.here you would find a sample code too.
EDIT:As you edited your Question see ,it just the logic which you need to convert it into runnable code here it is.
Use Below Method in Your Code called for each row, and make some calculation inside it.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat rowHeight=0.0;
//here it seems cell have 4 subview added on it.
//so if you could calculate the totla hieht of them.
//so what you really need to do.you just use hieght calculative Method for getting hieght of each of three UILabel
//you need to modify `setLabeltextWithVerticalAlignTop` method .
rowHeight= [self setLabeltextWithVerticalAlignTop:#"pass the correspondingText"];// suppose it returns some hieght for FisrtLabel.
//suppoose here you get the 20.0 height here
rowHeight= rowHeight+[self setLabeltextWithVerticalAlignTop:#"pass the correspondingText"];
// suppose it returns some hieght for secondUIlabel.
//suppoose here you get the 40.0 height here
rowHeight= rowHeight+ [self setLabeltextWithVerticalAlignTop:#"pass the correspondingText"];
// suppose it returns some hieght for ThirdUIlabel.
// suppoose here you get the 15.0 height here
//here you have totla height you just need to add some gapping floating value for all of three UIlabel.so that the could not overlap like as.
rowHeight= rowHeight+20.0;
//now you can return that total height
return rowHeight;
}
Note:This is just logic you need to convert it into runnable code.i am sure this can help.
I hope it may help you.
Implement the following methods
– (void) tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// cast cell, add label, expand labels etc
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [indexPath isEqualTo:[tableView indexPathForSelectedRow]] ? /* expanded height */ : 80 /* normal height */;
}
If you want the row to stay selected even after another row is selected then add a custom BOOL property to your custom cell, e.g. expanded, and use that to determine the height.
You can use tableView:didSelectRowAtIndexPath
In that method you can then create your code to unhide the body label, adjust the relative positions of everything else. Calculate the new height of the row and then call the Table View's reloadRowsAtIndexPath: withRowAnimation: method.
Sorry if there's not a lot of detail in that, but hopefully that should get you on the right track.
Ok, firstly... To expand you need something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
Now here is the catch:
You should calculate the size of the UITableViewCell (expanded and non-expanded)
Doing so when you are actually scrolling might be expensive and will give you a bad experience
My advice:
Calculate both sides, before you have actually conclude to build the UITableView, since you want to have dynamic sizes. If you don't and all cells will have the same size expanded, you can use what lammmert said.
NSIndexPath *selectedRow;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
selectedRow = indexPath;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if(indexPath == selectedRow){
//return your custom value
}
return 100;
}
I think it will look something like that
So, in order to do this, using expended UITableViewCell, i've created 2 different custom cells, at start the table is showing the first cell, when I click on the cell, the table is showing the second one. Its that easy - yeah!
So I have the UIViewController with the UITableView that implements the table delegate methods:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self.selectedCellIndexPath isEqual:indexPath])
{
return [self expandedCellHeight:indexPath];
}
else
{
return kRegularCellHeight;
}
}
-(CGFloat)expandedCellHeight:(NSIndexPath *)indexPath
{
CGSize maxSize = CGSizeMake(303, 200);
NSString* bodyText = [[self.data objectAtIndex:indexPath.row] objectForKey:kForumMessagesBody];
CGSize fitSize = [bodyText sizeWithFont:[UIFont systemFontOfSize:13] constrainedToSize:maxSize lineBreakMode:UILineBreakModeWordWrap];
CGFloat height = 384 - 69 + fitSize.height;
NSLog(#"expandedHeight: %f",height);
return height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Answer cell
if ([self.selectedCellIndexPath isEqual:indexPath])
{
cell = [tableView dequeueReusableCellWithIdentifier:[ForumCell expandedAnswerReuseIdentifier]];
if (cell == nil)
{
cell = [ForumCell expandedAnswerCell];
}
self.expandedCell = cell;
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:[ForumCell reqularAnswerReuseIdentifier]];
if (cell == nil)
{
cell = [ForumCell regularAnswerCell];
}
}
cell.labelMedia.text = [self.data objectAtIndex:indexPath.row];
return cell;
}
I also have custom cell, the class called ForumCell.h and ForumCell.m and it has 2 different XIB files: ForumRegularAnswerCell.xib and ForumExpandedAnswerCell.xib, I have the following code inside ForumCell.h:
+ (NSString*)reqularAnswerReuseIdentifier
{
return #"RegularAnswerCellReuseIdentifier";
}
+ (NSString*)expandedAnswerReuseIdentifier
{
return #"ExpandedAnswerCellReuseIdentifier";
}
+ (ForumCell*)regularAnswerCell
{
NSArray* objs = [[NSBundle mainBundle] loadNibNamed:#"ForumRegularAnswerCell" owner:self options:nil];
ForumCell* result = [objs objectAtIndex:0];
return result;
}
+ (ForumCell*)expandedAnswerCell
{
NSArray* objs = [[NSBundle mainBundle] loadNibNamed:#"ForumExpandedAnswerCell" owner:self options:nil];
ForumCell* result = [objs objectAtIndex:0];
return result;
}
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder];
if (self)
{
_originalCellHeight = self.frame.size.height;
_originalLblBodyHeight = self.lblBody.frame.size.height;
}
return self;
}
You can also use more than 2 xibs if you'd like its up to you. but this is the basics.
Enjoy!

UITableView infinite scrolling

How do I do an infinite scrolling in a UITableView? I know how to do it using a UIScrollView, in which apple has demonstrated in one of the WWDC's video. I tried doing the following in tableView:cellForRowAtIndexPath::
if (indexPath.row == [self.newsFeedData_ count] - 1)
{
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
but this fails. Any other idea?
If you need to know when you hit the bottom of the UITableView, become it's delegate (because it is a subclass of UIScrollView), and use the -scrollViewDidScroll: delegate method to compare the table's content height and it's actual scroll position.
EDIT (something like this):
- (void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat actualPosition = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height - (someArbitraryNumber);
if (actualPosition >= contentHeight) {
[self.newsFeedData_ addObjectsFromArray:self.newsFeedData_];
[self.tableView reloadData];
}
}
You can support infinite scroll with pull to refresh at the top and/or scroll continuously at the bottom with a spinner wheel using:
https://github.com/samvermette/SVPullToRefresh
SVPullToRefresh handles the logic when UITableView reaches the bottom. A spinner is shown automatically and a callback block is fired. You add in your business logic to the callback block.
Here's an example:
#import "UIScrollView+SVInfiniteScrolling.h"
// ...
[tableView addInfiniteScrollingWithActionHandler:^{
// append data to data source, insert new cells at the end of table view
// call [tableView.infiniteScrollingView stopAnimating] when done
}];
This project can be added to your project using CocoaPods or directly compiled into your project.
Here's a very quick and complete demo of an infinite scrolling UITableView I put together...
#interface InfiniteScrollViewController ()
#property (nonatomic) NSMutableArray *tableViewData;
#property (nonatomic) BOOL loadingMoreTableViewData;
#end
#implementation InfiniteScrollViewController
- (void)viewDidLoad {
self.tableViewData = [[NSMutableArray alloc] init];
[self addSomeMoreEntriesToTableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.tableViewData.count + 1;
}
- (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];
}
if (indexPath.row < self.tableViewData.count) {
cell.textLabel.text = [self.tableViewData objectAtIndex:indexPath.row];
} else {
cell.textLabel.text = #"Loading more data...";
// User has scrolled to the bottom of the list of available data so simulate loading some more if we aren't already
if (!self.loadingMoreTableViewData) {
self.loadingMoreTableViewData = YES;
[self performSelector:#selector(addSomeMoreEntriesToTableView) withObject:nil afterDelay:5.0f];
}
}
return cell;
}
- (void)addSomeMoreEntriesToTableView {
int loopTill = self.tableViewData.count + 20;
while (self.tableViewData.count < loopTill) {
[self.tableViewData addObject:[NSString stringWithFormat:#"%i", self.tableViewData.count]];
};
self.loadingMoreTableViewData = NO;
[self.tableView reloadData];
}
#end
'UITableView' is same as 'UIScrollView' in 'scrollViewDidScroll' method.
So, its easy to emulate infinite scrolling.
double the array so that head and tail are joined together to emulate circular table
use my following code to make user switch between 1st part of doubled table and 2nd part of doubled table when they tend to reach the start or the end of the table.
:
/* To emulate infinite scrolling...
The table data was doubled to join the head and tail: (suppose table had 1,2,3,4)
1 2 3 4|1 2 3 4 (actual data doubled)
---------------
1 2 3 4 5 6 7 8 (visualising joined table in eight parts)
When the user scrolls backwards to 1/8th of the joined table, user is actually at the 1/4th of actual data, so we scroll instantly (we take user) to the 5/8th of the joined table where the cells are exactly the same.
Similarly, when user scrolls to 6/8th of the table, we will scroll back to 2/8th where the cells are same. (I'm using 6/8th when 7/8th sound more logical because 6/8th is good for small tables.)
In simple words, when user reaches 1/4th of the first half of table, we scroll to 1/4th of the second half, when he reaches 2/4th of the second half of table, we scroll to the 2/4 of first half. This is done simply by subtracting OR adding half the length of the new/joined table.
*/
-(void)scrollViewDidScroll:(UIScrollView *)scrollView_
{
CGFloat currentOffsetX = scrollView_.contentOffset.x;
CGFloat currentOffSetY = scrollView_.contentOffset.y;
CGFloat contentHeight = scrollView_.contentSize.height;
if (currentOffSetY < (contentHeight / 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY + (contentHeight/2)));
}
if (currentOffSetY > ((contentHeight * 6)/ 8.0)) {
scrollView_.contentOffset = CGPointMake(currentOffsetX,(currentOffSetY - (contentHeight/2)));
}
}
P.S. - I've used this code on one of my apps called NT Time Table (Lite). If you want the preview, you can check out the app: https://itunes.apple.com/au/app/nt-time-table-lite/id528213278?mt=8
If your table can sometimes be too short, at the beginning of the above method you can add a if logic to exit the method when data count is say for example less than 9.
For me worked better scrollViewDidEndDragging: than scrollViewDidScroll:.
The second approach will send you each position during scroll and cause, if you are fetching remote resources you will hit your endpoint several times, which is not good.
Complete example based on #codafi solution with comments from #danielgomezrico about how to calculate contentHeight:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
willDecelerate:(BOOL)decelerate {
CGFloat actualPosition = scrollView.contentOffset.y;
CGFloat contentHeight = scrollView.contentSize.height - (self.tableView.frame.size.height);
if (actualPosition >= contentHeight) {
// fetch resources
[self.tableView reloadData];
}
}
Generally I override scrollViewDidEndDecelerating and inside it I put my code to request more data.
Example:
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height){
//put here your code
}
}
Recently I uploaded on GitHub a subclass of UITableView, that implements the infinite scroll.
You can download it here:
https://github.com/alchimya/iOS-LazyTableView
rather than overriding we can do this optimally in layoutSubviews.
Here's how I got it implemented. You can get to know more about the implementation here
- (void)layoutSubviews{
[super layoutSubviews];
if(self.delegateForViews){
CGPoint contentOffset = self.contentOffset;
if([self.delegateForViews noOfViews]>numOfReusableViews){
NSUInteger centerIndex=visibleViews.count/2;
NSUInteger noOfViews=[self.delegateForViews noOfViews];
UIView *centerView=[visibleViews objectAtIndex:centerIndex];
CGPoint centerViewOrigin=centerView.frame.origin;
CGSize centerViewSize=centerView.frame.size;
CGFloat offsetDifference=contentOffset.x-centerViewOrigin.x;
CGFloat offsetDifferenceAbs=fabs(contentOffset.x-centerViewOrigin.x);
if(offsetDifferenceAbs>=centerViewSize.width){
if(offsetDifference<0){
currentPosition--;
}else{
currentPosition++;
}
self.contentOffset=centerViewOrigin;
currentPosition=[self getPosition:currentPosition noOfViews:noOfViews];
[self.delegateForViews clearView:centerView];
[self.delegateForViews setupView:centerView forPosition:currentPosition];
for (int i=centerIndex-1; i>=0; i--) {
UIView* prevView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:prevView];
[self.delegateForViews setupView:prevView forPosition:
[self getPosition:currentPosition-1 noOfViews:noOfViews]];
}
for (int i=centerIndex+1; i<visibleViews.count; i++) {
UIView* nextView=[visibleViews objectAtIndex:i];
[self.delegateForViews clearView:nextView];
[self.delegateForViews setupView:nextView forPosition:
[self getPosition:currentPosition+1 noOfViews:noOfViews]];
}
}
}
}
}
One of the simple and that offered me everything i need is this class:
https://github.com/jakemarsh/JMStatefulTableViewController
You just need to subclass JMStatefulTableViewController and the it has 3 methods that you need to overwrite:
one that is called on init, to get the initial data
statefulTableViewControllerWillBeginInitialLoading
one when the user pull to refresh
statefulTableViewControllerWillBeginLoadingFromPullToRefresh
one when is called for the infinite scroll (next page)
statefulTableViewControllerWillBeginLoadingNextPage
This can be used from Cocoapods too.
scrollviewDidScroll will call when you move through the rows in tableview
func scrollViewDidScroll(_ scrollView: UIScrollView) {
//check for the visible rows
let indexpath = self.tableView.indexPathsForVisibleRows?.last
//check if the visible row last is equal to the total number of counts
if(indexpath?.last == self.listCount){
//code for adding data to the tableview and reload the table view.
}
}
look in the link for more details about indexPathForVisibleRows
https://developer.apple.com/documentation/uikit/uitableview/1614885-indexpathsforvisiblerows

Custom UITableView Dynamic Cell Height

I have search and searched through endless blogs and articles on how to determine a dynamic height for a custom UITableViewCell and its detailed text. I have really had a hard time finding any good documentation on this.
What I need to do is have the cell grow according to the the text inside but never go below a height of 70.
I have tried several of the answers for this type of question here on StackOverflow but none of them worked. My whole app is just about finished but I really need to get this accomplished before I release and its troublesome.
Here is what I am trying but I just get a slop of cells overlapping each other. From what I read I need to find the frame if the custom cell or textview in the cell as well but I am not sure how to do that or mix them all together to return one height.
Any Help would be greatly appreciated Thank you!
- (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath
*) indexPath
{
CGSize aSize;
aSize = [[(Tweet*)[tweets objectAtIndex:indexPath.row]tweet] sizeWithFont:[UIFont
systemFontOfSize:14]
constrainedToSize:CGSizeMake(300.0, 1000.0)
lineBreakMode:UILineBreakModeTailTruncation];
return aSize.height;
}
I had a similar issue a while back and this helped me tremendously.
#define PADDING 10.0f
- (CGFloat)tableView:(UITableView *)t heightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *text = [self.items objectAtIndex:indexPath.row];
CGSize textSize = [text sizeWithFont:[UIFont systemFontOfSize:14.0f] constrainedToSize:CGSizeMake(self.tableView.frame.size.width - PADDING * 3, 1000.0f)];
return textSize.height + PADDING * 3;
}
Hey there so you are going to need to store the list of strings in an NSArray and then you are going to need to calculate the height of the nsstring using sizeWithFont:constrainedToSize: the documentation is found here http://developer.apple.com/library/ios/#documentation/UIKit/Reference/NSString_UIKit_Additions/Reference/Reference.html
so your tableView method should look something like
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:[UIFont fontWithName:"Helvetica" size:9] forKey: NSFontAttributeName];
CGSize cell_size = [string boundingRectWithSize:CGSizeMake(300,999)
options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin
attributes:stringAttributes context:nil].size;
if (cell_size.height > 70)
{
return cell_size.height;
}
else
{
return 70;
}
}
EDIT : THIS has been updated for iOS 7
I tried many solutions, but the one that worked was this, suggested by a friend:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
int height = [StringUtils findHeightForText:yourLabel havingWidth:yourWidth andFont:[UIFont systemFontOfSize:17.0f]];
height += [StringUtils findHeightForText:yourOtherLabel havingWidth:yourWidth andFont:[UIFont systemFontOfSize:14.0f]];
return height + CELL_SIZE_WITHOUT_LABELS; //important to know the size of your custom cell without the height of the variable labels
}
The StringUtils.h class:
#import <Foundation/Foundation.h>
#interface StringUtils : NSObject
+ (CGFloat)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font;
#end
StringUtils.m class:
#import "StringUtils.h"
#implementation StringUtils
+ (CGFloat)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font {
CGFloat result = font.pointSize+4;
if (text) {
CGSize size;
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{NSFontAttributeName:font}
context:nil];
size = CGSizeMake(frame.size.width, frame.size.height+1);
result = MAX(size.height, result); //At least one row
}
return result;
}
#end
It worked perfectly for me. I had a Custom Cell with 3 images with fixed sizes, 2 labels with fixed sizes and 2 variable labels. Worked like a charm. Hope it works for you too.
Best regards, Alexandre.
iOS 7
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
{
NSString *text = #"Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello HelloHello HelloHello HelloHello HelloHello HelloHello Hello";
CGRect rect = [text boundingRectWithSize:CGSizeMake(280.0f, CGFLOAT_MAX) options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin attributes:#{
NSFontAttributeName: [UIFont fontWithName:#"Hellvetica" size:14.0f]
} context:nil];
return CGSizeMake(320.0f, ceil(rect.size.height) + 10.0f);
}
I just wrote about this problem and how I finally decided to solve it. You can read about it here: Dynamic UITableView Cell Height Based on Contents
Basically, I created a UITableView subclass that automates the handling and calculation of dynamic cell height for both default and custom cells. It is not a silver bullet and probably needs to be extended and tweaked, but I have used it as is in several apps with good result.
You can grab the code here: https://github.com/danielsaidi/AutoSizeTableView
Hope it helps!
(...and if it didn't, I'd love to hear why not)
Use UITableViewDelegate method:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// here you need to get height of your textlabel in your custom cell.
MyCustomCell *myCell = (MyCustomCell *)[tableView cellForRowAtIndexPath:indexPath];
return myCell.myTextLabel.frame.size.height + 20;
}
Something like this