how calculate UITableViewCell height seeing content - ios

I need to determine cell height and place in this cell text which has images inside, for example:
some text there
image
sometext there
image
... ect.
How to implement this? Suggest pls.
Thnx.
And I want divide my paragraphs in individual cells like this:

Because the content is dynamic, one way to do it is to calculate the height of the cell ahead of heightForRowAtIndexPath (or equivalent method) being called and store those values in an array.
-(void)calculateCellHeights:(NSArray *)contentArray
{
self.heightArray = [[NSMutableArray alloc] initWithCapacity: contentArray.count];
for( MyCellContent *content in contentArray )
{
CGFloat totalHeight = 0;
totalHeight += content.image.size.height;
CGSize titleSize = [content.title sizeWithFont:self.myTitleFont];
totalHeight += titleSize.height;
CGSize textContentSize = [content.textContent sizeWithFont:self.myTextContentFont];
totalHeight += textContentSize.height;
[self.heightArray addObject:#(totalHeight)];
}
}
-(CGFloat) tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*) indexPath
{
return self.heightArray[[indexPath.row floatValue]];
}
This is assuming only one section and the use of regular text strings instead of NSAttributedStrings, and doesn't handle padding between interface elements but you get the picture.

Related

Activity shown in custom cell view

I have to create a screen displaying various activity in the user's account such as picture like, uploading images and videos. Just like this-
I thought of using custom TableViewCell for which I need to make two different custom cells in a same table view. I had no problem while making a custom cell for the 2nd activity but am facing problem for the first activity as I have to add an UIImageView for the picture and video. The height of view is not increasing along with UIImage added. Can anyone tell me how can I make two different shapes of custom cells or should I use some other way to show the activities?
You should calculate cell height for different cells. Here is how I do with this:
+(CGFloat)heightForBubbleWithObject:(MessageModel *)object
{
CGSize retSize = object.size;
if (retSize.width == 0 || retSize.height == 0) {
retSize.width = MAX_SIZE;
retSize.height = MAX_SIZE;
}else if (retSize.width > retSize.height) {
CGFloat height = MAX_SIZE / retSize.width * retSize.height;
retSize.height = height;
retSize.width = MAX_SIZE;
}else {
CGFloat width = MAX_SIZE / retSize.height * retSize.width;
retSize.width = width;
retSize.height = MAX_SIZE;
}
return 2 * BUBBLE_VIEW_PADDING + retSize.height;
}
in ios 8 and above put this magical lines and make sure the constraints are properly given
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0;
there is a wonderful answer about this please refer it for the details Using Auto Layout in UITableView for dynamic cell layouts & variable row heights

Create a view using collection view in ios

I am trying to create a view similar to the attached image below. there is a variable sized width. I have marked text as black as there is a copyright issue.
Can anyone please look into the same and put some code so that it can help me somewhere.
Do I need to implement Custom Collection View Layout?
Please help me.
This is response to your comment you need to add 3 extra lines of code in SGSStaggeredFlowLayout
NSArray* arr = [super layoutAttributesForElementsInRect:rect];
// THIS CODE SEPARATES INTO ROWS
NSMutableArray* rows = [NSMutableArray array];
NSMutableArray* currentRow = nil;
NSInteger currentIndex = 0;
BOOL nextIsNewRow = YES;
for (UICollectionViewLayoutAttributes* atts in arr) {
if (nextIsNewRow) {
nextIsNewRow = NO;
if (currentRow) {
[rows addObject:currentRow];
}
currentRow = [NSMutableArray array];
}
if (arr.count > currentIndex+1) {
UICollectionViewLayoutAttributes* nextAtts = arr[currentIndex+1];
if (nextAtts.frame.origin.y > atts.frame.origin.y) {
nextIsNewRow = YES;
}
}
[currentRow addObject:atts];
currentIndex++;
}
if (![rows containsObject:currentRow]) {
[rows addObject:currentRow];
}
It works like charm :)
You can set size for every item by impelmenting
UICollectionViewDelegateFlowLayout protocol, and calculate item width using even/odd formula.
-(CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)collectionView.collectionViewLayout;
NSInteger itemsPerRow = 0;
NSInteger contentSizeWidth = 0;
NSInteger num = indexPath.row;
if (num % 2)
{// odd
itemsPerRow = 2;
}
else {
// even
itemsPerRow = 3;
}
contentSizeWidth = collectionView.frame.size.width- (flowLayout.minimumInteritemSpacing*(itemsPerRow-1))-flowLayout.sectionInset.left-flowLayout.sectionInset.right;
return CGSizeMake(contentSizeWidth/itemsPerRow, 100);
}
if you are trying to do this without any framework, you need to develop your own algroithm to calculate the width of each cell.
first, you need to calculate the width of text plus margin maybe border as well.
Second, calculate how many items are gonna be placed in given row. try to add 3 togther , if the total width excess the uicollection width, it means the third text should go to the next cell. if it is less than the collection width,it means you can add try to the 4th text.
third, caculate the width of each cell in each line base on how many cells are gonna placed on that line and their own width.
changing the uicollectionview width should not be diffculty since collectionviewcells are darw from left to right then top to bottom.

Collapsing UICollectionView

I'm having a fight with UICollectionView to adopt it to my needs.
I'm trying to build a collapsing tag cloud. All elements can dynamically change size based on text inside.
When collection is displayed, I would like to display only first row and hide rest of content.
If there is more content than just for one row I want to show a button as a last item in first row - after selecting it, I will change collection size to fit it's content.
First step:
After click:
I was able to achieve desired effect with this code:
- (void) updateFrame
{
if(self.showFull)
{
self.showMoreButton.hidden = YES;
self.bottomConstrain.constant = 0;
self.frame = CGRectMake(0, 0, self.collectionView.frame.size.width, self.collectionView.contentSize.height);
}
else
{
self.showMoreButton.hidden = NO;
self.bottomConstrain.constant = self.originalConstrainValue;
self.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
}
}
But button in my case is placed completely outside the collection:
I'm hitting my head against a brick wall how to make this button to be a part of collection view.
It's the first time I'm playing with UICollectionView and UICollectionViewFlowLayout and it's too complex for me still to understand where would be best spot to put it.
One idea I had was to play a button over UICollectionView and position it at the end of first row - but I don't know how to get first row size from collection. With this approach it will be problematic also to cover last item in a row.
I think the best will be to put this element as a UICollectionViewCell, but I have no idea how to approach that - how to predict where it will placed and how to hide it later.
Any ideas will be highly appreciated.
Current template project is here:
https://www.zipshare.com/download/eyJhcmNoaXZlSWQiOiJjNTg2MGFkNC1mYWYxLTRlMzItOTA1YS1hMWFjOGFkMjMzYjUiLCJlbWFpbCI6Imdya3J1a293c2tpQGdtYWlsLmNvbSJ9
I'd suggest putting this button as a different cell INSIDE the collection view. Then, in collectionView:didSelectItemAtIndexPath: you would check to see if the special item was selected and react accordingly. I would let this button always be the last item in the collection view so you know where it is. When the items have expanded and everything is displayed, make sure you flip your logic and compress the collection view when this special cell is pressed.
You'll need to save some state to do this:
1.The array of tags
whether or not the collection view is expanded or not
Once you have this info, you can just flip the bits and reload the collection view section.
I've managed to achieve what I wanted with this code (thanks for atreat for suggestion).
I'm estimating which element won't fit in first row, and pushing "More" tag into tags array.
There is some logic also to make sure that More button will fit after last element in this row, if not it's pushed instead of this element. Maybe some will find it usefull.
UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout*)self.collectionView.collectionViewLayout;
CGFloat collectionWidth = self.frame.size.width;
CGFloat lastFittingItemRight = 0.0f;
CGFloat spacingBetweenElements = flowLayout.minimumInteritemSpacing;
CGFloat sectionInsetLeft = flowLayout.sectionInset.left;
CGFloat sectionInsetRight = flowLayout.sectionInset.right;
CGFloat rightEdge = sectionInsetLeft;
for (int i = 0; i < [self collectionView:self.collectionView numberOfItemsInSection:0]; i++)
{
CGSize elementSize = [self collectionView:self.collectionView layout:flowLayout sizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:0]];
CGFloat elementWidth = elementSize.width;
rightEdge += elementWidth;
if(rightEdge > collectionWidth - sectionInsetRight)
{
self.moreButtonIndexPath = [NSIndexPath indexPathForItem:i inSection:0];
break;
}
lastFittingItemRight = rightEdge;
rightEdge += spacingBetweenElements;
}
if(self.moreButtonIndexPath)
{
NSString* showMoreText = #"More";
CGSize moreButtonSize = [self sizeForTagWithText:showMoreText];
CGFloat rightEdgeMoreButton = lastFittingItemRight + spacingBetweenElements + moreButtonSize.width;
NSInteger moreButtonIndex = self.moreButtonIndexPath.row;
if(rightEdgeMoreButton > collectionWidth - sectionInsetRight)
{
moreButtonIndex = moreButtonIndex-1;
}
[self.tags insertObject:showMoreText atIndex:moreButtonIndex];
}

iOS - my heightForRowAtIndexPath implementation causes performance issues, why?

I use a table view where I override heightForRowAtIndexPath. When I run the Xcode profiler I found the following:
I want to calculate the height based one object's property. I use the same table view for two kind of objects, User and Post. My implementation currently looks like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *text;
Post *cellPost;
User *cellUser;
if (_pageType == FOLLOWERS || _pageType == FOLLOWING) {
cellUser = [self.users objectAtIndex:indexPath.row];
text = cellUser.userDescription.text;
if (text == nil) {
return 70;
}
} else {
cellPost = [self.fetchedResultsController objectAtIndexPath:indexPath];
text = cellPost.text;
}
CGSize boundingSize = CGSizeMake(245, CGFLOAT_MAX);
CGSize requiredSize = [text sizeWithFont:[UIFont fontWithName:#"TisaMobiPro" size:15]
constrainedToSize:boundingSize
lineBreakMode:NSLineBreakByWordWrapping];
CGFloat textHeight = requiredSize.height;
CGFloat cellHeight = textHeight + 40;
if ([cellPost.text isEqualToString:self.post.text]) {
cellHeight += 44;
if (cellHeight < 114) {
cellHeight = 114;
}
} else {
if (cellHeight < 70) {
cellHeight = 70;
}
}
if (cellPost.repostedBy != nil && cellPost.youReposted.boolValue == NO && _pageType != CONVERSATION) {
cellHeight += 27;
}
return cellHeight;
}
If I remove most of the code and only have, e.g. return 100 the tableView's scrolling performance is much improved. Can you spot or give suggestions on something in my implantation that could cause this performance issue?
Heights are calculated before the – tableView:cellForRowAtIndexPath: is called and for all the number of cells.
If you are deploying on iOS7 you can use the - tableView:estimatedHeightForRowAtIndexPath:, using that method you can defer this calculation while scrolling the tableview. That means that the method tableView:heightForRowAtIndexPath: is called basically when a cell is displayed. The estimation could be a fixed number close to the average of your TVC or based on some math faster than the one that you implemented.
Thanks to the estimation the TV can set its content size an scroll bar height and doesn't need to calculate each height for 100 of rows in one shot. The payback could be a lag during scrolling because the TV is calculating the actual row height.
If you are targeting iOS7 you can use the NSTableView property: estimatedRowHeight.
From the Apple docs:
Providing a nonnegative estimate of the height of rows can improve the
performance of loading the table view. If the table contains variable
height rows, it might be expensive to calculate all their heights when
the table loads. Using estimation allows you to defer some of the cost
of geometry calculation from load time to scrolling time.
Also:
Every time a
table view is displayed, it calls tableView:heightForRowAtIndexPath:
on the delegate for each of its rows
If you have to use tableView:heightForRowAtIndexPath: cache the heights as #Waine suggests.

iOS UITableView AutoSizing Cell with Section Index

I have an app where I load a large plist file in a tableview. This plist file is as a book. It contains chapters and lines. Each row has different length depending on the line length and therefore I need to resize the cell automatically.
I am using storyboard and standard tableview and cell. Cell style=basic and the cell label is set to text=plain lines=0 linebreaks=wordwrap
Up to there, no problem resizing the cell height to the proper size. As the cell height is defined before the text is inserted in the label we have to do it by the well known method of using CGsize and I do it like that (it's working fine)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:
(NSIndexPath*)indexPath
{
NSUInteger section = [indexPath section];
NSString *key = [chapters objectAtIndex:section];
NSArray *linesSection = [lines objectForKey:key];
NSString* theText = [linesSection objectAtIndex:indexPath.row];
int labelWidth = LW_CHAPTERINDEX_10;
if (chapters.count < 100){
if (chapters.count < NB_MIN_CHAP){
labelWidth = LABELWIDTH;
} else {
labelWidth = LW_CHAPTERINDEX_10;
}
}
CGSize textSize = [theText sizeWithFont:[UIFont systemFontOfSize:14]
constrainedToSize:CGSizeMake(labelWidth, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
return textSize.height;
}
The problem is the hardcoding depending on the index. For now I have 3 possibilites.
No index
Index with numbers below 10
Index with numbers below 100
and in the code this part
int labelWidth = LW_CHAPTERINDEX_10;
if (chapters.count < 100){
if (chapters.count < NB_MIN_CHAP){
labelWidth = LABELWIDTH;
} else {
labelWidth = LW_CHAPTERINDEX_10;
}
}
is the hardcoding depending on the 3 possibilities.
I find this was of doing weird, especially if apple will start to deliver more different screen sizes.
QUESTION
How can I get the index width at runtime to determine my label width ?
For example i would like to program something like screen.width - index-width to get the label width.
Or any other that should allow it to be dynamical and no more statically hardcoded.
Unfortunately there is no (standard) way you can directly access the section index subview. However, you can use the methods for calculating the CGSize of text to determine dynamically the width of the section index.
You could determine all possible strings to be returned by sectionIndexTitlesForTableView: beforehand and calculate the necessary size, perhaps padding with some extra pixels left and right. Maybe it is necessary to make some experiments to determine the correct font and size.
Now your approach with something like screenSize.width - textSize.width - 2*PADDING should be viable. The only hardcoded thing is now the padding, which should not break things when new screen sizes are introduced.
Ok, to save other people a few hours of work....I laboriously tested various fonts and sizes and margins, comparing them to a UIView hierarchy dump of an actual table view with an index, to arrive at this method which will return the width of a table index, so that the bounds of the table view cell content can be calculated as table_width - index_width. I will point out that there is often another 20 pixel right-side amount reserved for an accessory view.
I also discovered that the cell.contentView.bounds is NOT correctly set until AFTER cellForRowAtIndexPath and willDisplayCell methods are called, so trying to grab the value during those calls is doomed to fail. It is set correctly by the time viewDidAppear is called.
I have no idea how to calculate the index width if the device language is set for a non-English alphabet.
- (CGFloat) getMaxIndexViewWidth
{
CGFloat indexMargin = 21.0;
NSArray *sectionTitles = [self sectionIndexTitlesForTableView:nil]; //NOTE -- if multiple tables, pass real one in
CGFloat maxWidth11 = CGFLOAT_MIN;
for(NSString *title in sectionTitles)
{
CGFloat titleWidth11 = [title sizeWithFont:[UIFont fontWithName:#"Helvetica-Bold" size:11.0f]].width;
maxWidth11 = MAX(maxWidth11, titleWidth11);
}
return maxWidth11+indexMargin;
}

Resources