I have UITableView with custom cells, which contain uilabels.
I am using it to show comments and it must have flexible height.
How can I do it?
I have code, which counting number of rows with help of uilabel.text length, but it is wrong way. Have you any ideas?
Use UILabel with numberOfLines set to zero.
Then, for making sure that your label actually fits into the cell, get the dimensions from the NSString UIKit extension like this;
CGFloat cellWidth = 320.0f; //example width....
labelSize = [label.text sizeWithFont:label.font constrainedToSize:CGSizeMake(cellWidth, FLT_MAX)];
From the NSString reference;
sizeWithFont:constrainedToSize:
Returns the size of the string if it were rendered and constrained to
the specified size.
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size
Parameters font The font to use for computing the string size. size
The maximum acceptable size for the string. This value is used to
calculate where line breaks and wrapping would occur. Return Value The
width and height of the resulting string’s bounding box.
Create a class method of the comment cell class that calculates the height of text needed for cell's height. Also create a class method that returns cell's default font, so you can always calculate proper cell height in cell's layoutSubviews and in UITableViewDelegate's cellHeightForRowAtIndexPath methods.
e.g.
+(UIFont *) defaultCommentFont {
return [UIFont italicSystemFontOfSize:13];
}
+(CGSize) sizeOfComment:(NSString *)commentText maxWidth:(CGFloat)maxWidth {
return [commentText sizeWithFont:[[self class] defaultCommentFont] constrainedToSize:CGSizeMake(maxWidth, MAX_FLOAT)];
}
- (void)layoutSubviews {
[super layoutSubviews];
static CGFloat margin = <# margin #>;
CGSize commentSize = [[self class] sizeOfComment:self.commentText maxWidth:self.contentView.frame.size.width - 2 * margin];
self.commentLabel.frame = CGRectMake(margin, margin, commentSize.width, commentSize.height);
}
(sorry if there are any mistakes in the code - using only the eye-based parser ;))
Having this, when you try to return a proper height of cell in UITableViewDelegate's method, you don't have to use cellForRowAtIndexPath: method, because this, in some cases, can cause an uncontrolled recursion resulting in stack overflow.
And, like Till said, set the comment label's property numberOfLines to 0.
Related
I'm working on an RSS Feed and I'm looking at resizing the TableViewCell to suit the Title/Description information that comes in, I'd like the Cell to show all the text rather than cut it off... Does anybody know a good way of doing this or know of a good tutorial?
Thanks.
You need to calculate the height for the cell holding your feed item data and return it when the table asks for it in tableView:heightForRowAtIndexPath:.
Furthermore you need to say the UILabel not to truncate your text. For this, check out the methods under Sizing the Label’s Text
You should make custom UITableViewCell for that.Then set it correct autosizing parameters in your .xib file
And then, in your cell .m file you should implement layoutSubviews method in such way
- (void) layoutSubviews
{
[super layoutSubviews];
CGRect newFrame = rssLabel.frame;
CGSize maximumLabelSize = CGSizeMake(rssLabel.frame.size.width,9999);
CGSize expectedLabelSize = [rssLabel.text sizeWithFont:tweetLabel.font constrainedToSize:maximumLabelSize lineBreakMode:rssLabel.lineBreakMode];
int height = expectedLabelSize.height;
newFrame.size.height = height;
rssLabel.frame = newFrame;
}
And surely, I forgot to say that you should calculate the height of your row
The best approach is to subclass UITableViewCell. In this class implement a class method to return you height of cell. in tableview's delegate method tableView:heightForRowAtIndexPath: call this method and pass your text to it. Use NSString's sizeWithFont constrainedToSize lineBreakMode: method and calcilate height. set your label's property noOfLines to 0. And you're good to go.
For example:-
+(CGFloat)heightForText:(NSString *)value{
CGSize size = [value sizeWithFont:[UIFont systemFontOfSize:13] constrainedToSize:CGSizeMake(320, MAXFLOAT)];
if(size.height<MINIMUM_CELL_HEIGHT){
return MINIMUM_CELL_HEIGHT;
}else{
return size.height;
}
}
A subquestion is:
How do I determine what the built-in internal margins of a UITextview are?
I have a long master string of text that I am trying to split into separate UITextView pages that I can then scroll from page to page inside a UIScrollView. I use the following method to determine what the height of a string in a UITextView is and whether the string is over the height limit:
-(NSNumber *)getHeightByWidth: (NSString *) myString
mySize: (UIFont *) mySize
myWidth: (NSNumber *) myWidth
{
int intMyWidth = [myWidth intValue];
CGSize boundingSize = CGSizeMake(intMyWidth, CGFLOAT_MAX);
CGSize requiredSize = [myString sizeWithFont:mySize constrainedToSize:boundingSize lineBreakMode:UILineBreakModeWordWrap];
NSNumber *retNumber = [[NSNumber alloc] initWithFloat:requiredSize.height];
return retNumber;
[retNumber release];
}
I call the getHeightByWidth method using the following cellFont as the input for mySize:
UIFont *cellFont = [UIFont fontWithName:#"Arial" size:14.0];
The UITextView is 320 pixels wide, but I notice that the text doesn't go from the left edge to the right edge as there are internal margins which look to be around 10 pixels on each side. So when I call getHeightByWidth I set myWidth = (320 - 10 - 10); But after building strings to fit within the UITextView, there are usually gaps on the last row that could be filled with the next words in the master string.
Can anyone tell me why these gaps on the last row of the text occur using this process for UITextView?
The built-in margins are represented by the property contentInset.
Also you can configure the margins yourself.
If you have your text view in IB, look for Content insets. The values must be 0, but it still displays some margin. Trying setting them to negative values such as -4 or -8.
In the code, do something like-
myTextView.contentInset = UIEdgeInsetsMake(-4,-8,0,0);
You have to set these values according to what you find suitable.
Let's say I have two UILabels positioned vertically below each other in a UITableViewCell. The line break mode is set to UILineBreakModeWordWrap for both.
Their horizontal size is fixed, but both can stretch vertically based on how much text they display. How do I position the one that's below dynamically so that they would never overlap?
i use this function to get the height of the text, and then set the second label height to the result.
- (CGFloat)HeightOfText:(NSString *)textToMesure widthOfLabel:(CGFloat)width
{
UIFont *textFont = [UIFont systemFontOfSize:16];
CGSize ts = [textToMesure sizeWithFont:textFont constrainedToSize:CGSizeMake(width-20.0, FLT_MAX) lineBreakMode:UILineBreakModeWordWrap];
return ts.height+25.0; //you can change the last number to fit the space you wish to have between the labels.
}
and you use it like that:
NSString *firstLabelText = #"the text";
CGFloat textSize = [self HeightOfText:firstLabelText widthOfLabel:firstLabel.frame.size.width];
then use "textSize" to set the second label height.
hope it will help.
Try -[UILabel sizeThatFits:]. Say you're laying the labels out in a 300px wide column, you can pass in a size of (300,99999), and it'll tell you the size it actually needs to fit that text in. You can use that to adjust the frames of your labels appropriately.
I've done something similar by resetting the frame of the view below (assuming you've already done sizeWithFont or sizeThatFits and resized the labels). Similar to this:
- (void)positionView:(UIView *)aView belowView:(UIView *)anotherView withPadding:(int)padding
{
CGRect aFrame = aView.frame;
if (anotherView)
{
aFrame = (anotherView.frame.origin.y + anotherView.frame.size.height);
}
aFrame.origin.y += padding;
aView.frame = aFrame;
}
Is there a way to get the correct size of an NSString using:
- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
that doesnt get thrown off by 2 or 3 hundred character strings. At the moment if I try to use this method on these long strings it incorrectly calculates them and I end up with lots of whitespace at the bottom of the UITextView.
I've tried using UILineBreakModeWordWrap and UILineBreakModeCharacterWrap.
the resizing is being done in
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat result = 44.0f;
NSString* text = nil;
CGFloat width = 0;
CGFloat tableViewWidth;
CGRect bounds = [UIScreen mainScreen].bounds;
tableViewWidth = bounds.size.width;
width = tableViewWidth - 150;
text = stringWithLongWords;
if (text) {
CGSize textSize = { width, 20000.0f };
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:10.0f] constrainedToSize:textSize lineBreakMode:UILineBreakModeWordWrap];
size.height += 50.0f;
result = MAX(size.height, 44.0f+30.0f);
}
return result;
}
UITextView is not exactly like a UILabel wrapped in a UIScrollView. It has line spacing different from the font size and margins that sizeWithFont:constrainedToSize:linkBreakMode: doesn't account for.
Knowing your font size you might be able to calculate the # of lines and take line spacing into account. You can guess at the margins and try to trick sizeWithFont: to give a more useful answer.
The popular solutions seem to be:
just use a UILabel if you don't need any UITextView functionality
if you need hyperlinks, overlay UIButtons that look like hyperlinks over a UILabel
use an off-screen UITextView and its sizeToFit method to get a real answer
I had no luck w/ the 3rd option but it sounds like it should work, so perhaps I did something wrong.
I'm going to try using a UILabel and overlaying buttons for hyperlinks. We'll see how that turns out.
If that fails, there is always the option taken by Loren Brichter (of Tweetie fame): draw everything into a UIView yourself using CoreGraphics.
Good luck!
Check out this post How do I size a UITextView to its content?
It looks like textView.contentSize.height should work (with the caveat that the the correct contentSize is only available after the UITextView has been added to the view with addSubview)
You said that you have a UITableView with differing heights. Have you set the reuse identifier to the same thing for all of the cells? It could be that older cells with their height already set are being reused. If this is the problem, you should resize the cell again when it's being reused.
The best solution I have found so far is to have a separate hidden UITextView with the same font settings, and set its text. After that its contetSize should be accurate.
The width you are using is the width for your UITextView... but you aren't concerned with that width, you are concerned with the width of the actual text area nested inside the text view.
UITextViews, by default, have padding around their borders to produce a space in-between the typed text and the edge of the UITextView a few pixels wide (and long for the top)... To get the correct size you shouldn't use
textView.frame.size.width
but rather,
textView.frame.size.width-(textView.contentInset.left+textView.contentInset.right+textView.textContainerInset.left+textView.textContainerInset.right+textView.textContainer.lineFragmentPadding/*left*/+textView.textContainer.lineFragmentPadding/*right*/)
^Which takes the width of the UITextView and subtracts out all the padding so you are left with the width of just the type-able text area.
Same goes for height except for lineFragmentPadding doesn't have a bottom so you only subtract it out once instead of twice.
The final code is something like this:
CGSize textViewContentSize = CGSizeMake(theTextView.frame.size.width-(theTextView.contentInset.left+theTextView.contentInset.right+theTextView.textContainerInset.left+theTextView.textContainerInset.right+theTextView.textContainer.lineFragmentPadding/*left*/+theTextView.textContainer.lineFragmentPadding/*right*/), theTextView.frame.size.height-(theTextView.contentInset.top+theTextView.contentInset.bottom+theTextView.textContainerInset.top+theTextView.textContainerInset.bottom+theTextView.textContainer.lineFragmentPadding/*top*//*+theTextView.textContainer.lineFragmentPadding*//*there is no bottom padding*/));
CGSize calculatedSize = [theTextView.text sizeWithFont:theTextView.font
constrainedToSize:textViewContentSize
lineBreakMode:NSLineBreakByWordWrapping];
CGSize adjustedSize = CGSizeMake(ceilf(calculatedSize.width), ceilf(calculatedSize.height));
Inspired by #MrNickBarker's answer, here's my solution:
CGFloat width = 280.0f;
UITextView *t = [[UITextView alloc] init];
[t setFont:[UIFont systemFontOfSize:17]];
[label setText:#"some short or long text, works both"];
CGRect frame = CGRectMake(0, 0, width, 0);
[t setFrame:frame];
// Here's the trick: after applying the 0-frame, the content size is calculated and can be used in a second invocation
frame = CGRectMake(0, 0, width, t.contentSize.height);
[t setFrame:frame];
The only issue remaining for me is that this doesn't work with modified insets.
Still can't believe such twists are required, but since -[NSString sizeWithFont:forWidth:lineBreakMode:] does not respect insets, paddings, margins, line spacings and the like, it seems this is the only working solution at the moment (i.e. iOS 6).
i want to calculating no of lines when we give text in UITableviewcell?i have inserted text through UITextview which is in UITableview cell.but i want to increase the height of cell
based on UItextview which is in UITableview cell.suppose if it has 4 lines ,one height...otherwise other height...i want to also calculate how many lines are in UITextview...and also set height at runtime?any help pls..?
To calculate the approximate size of your UITextView based on a string, try NSString's - (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode method.
For example:
NSString *myText = #"Lorem ipsum dolor sit amet, consectetur adipisicing...";
CGSize newSize = [myText sizeWithFont:[textView font]
constrainedToSize:CGSizeMake([textView frame].size.width, MAXFLOAT)
lineBreakMode:UILineBreakModeWordWrap];
CGFloat newTextViewHeight = newSize.height;
CGFloat newTableViewCellHeight = newTextViewHeight + 42;
...where textView refers to your UITextView. (I've also assumed that the line break mode of your UITextView is UILineBreakModeWordWrap; if it isn't, make sure you change the appropriate code.)
You can calculate the fitted size of a UILabel based on the NSString content using the UIKitAdditions category on NSString. These methods include...
-sizeWithFont:
-sizeWithFont:constrainedToSize:
-sizeWithFont:constrainedToSize:lineBreakMode:
-sizeWithFont:forWidth:lineBreakMode:
-sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
In my implementations of this pattern, I typically use the third one, -sizeWithFont:constrainedToSize:lineBreakMode:.
The most efficient implementations of variable-height UITableViewCells will only calculate the size once per displayed string. This is typically an expensive operation and you definitely don't want expensive operations being performed frequently while scrolling - your scrolling FPS will drop through the floor. This means caching the calculated height somewhere, and for me that somewhere is the model object associated with the specific row.
Personally, I create a custom subclass of UITableViewCell whenever I'm implementing a variable-height table cell. I then create a class method that simply performs the sizeWithFont:constrainedToSize:lineBreakMode: operation given a model object which will contain the string being displayed as a parameter. The table view controller implementation's of tableView:heightForRowAtIndexPath: then looks something like this:
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath {
CGFloat height;
DataObject *rowData = [self.dataArray objectAtIndex:indexPath.row];
if (rowData.cachedHeight) {
height = [rowData.cachedHeight doubleValue];
} else {
height = [CustomTableViewCell heightForRowWithString:rowData.contentStr];
rowData.cachedHeight = [NSNumber numberWithDouble:height];
}
return height;
}
N.B. You could/should probably use a UILabel, rather than a UITextView, since you don't need the scrolling behavior of the UITextView.