I'm debugging a strange text wrapping problem on my device that I have tracked down to some kind of modification in the UITextView contentInset value starting in XCode 4.
Here is the sample code I am using to showcase this bug:
NSString *message = #"How are you doing?";
CGSize messageDimensions = [message sizeWithFont:[UIFont systemFontOfSize:15.0]
constrainedToSize:CGSizeMake(self.view.frame.size.width,9999)
lineBreakMode:UILineBreakModeWordWrap];
CGFloat xOffset = 20;
CGFloat yOffset = 20;
CGRect textViewFrame = CGRectMake(xOffset, yOffset, messageDimensions.width+16, messageDimensions.height+16);
self.textView = [[[UITextView alloc] initWithFrame:textViewFrame] autorelease];
self.textView.font = [UIFont systemFontOfSize:15.0];
self.textView.text = message;
self.textView.editable = NO;
[self.view addSubview:self.textView];
Attached are two screenshots running code on the iOS 5 simulator and on my iPhone 4.
Here is the iPhone 4 running iOS5:
Here is the simulator running iOS5.0:
As you can see, it is wrapping in one but not the other. In order to prevent wrapping on my iOS4 device I have to change this line:
CGRect textViewFrame = CGRectMake(xOffset, yOffset, messageDimensions.width+16, messageDimensions.height+16);
To have a padding of +17 not +16.
Can anyone tell me if this is a known bug, and if the UITextView padding amount is a constant value somewhere in UIKit to which I can refer instead of hard-coding it?
Thanks!
Related
I am newbie to ios and i am learning tableView with custom cells,I have make a code for that successfully,but now i have a problem that a label in which i am setting title text is cutting right side to device width,i want that text to come in next line if it is cutting with device width.can anybody please tell?
My code is as below
code
UILabel *titleLbl = (UILabel *)[cell viewWithTag:102];
CGRect frame = self.titleLbl.frame;
frame.size.height = [self getHeightforController:self.titleLbl];
self.titleLbl.frame = frame;
titleLbl.text = str;
here durationtxt is a quite long string and its cutting right side.
use this function where you need dynamic height for all devices it will work
- (CGFloat)getHeightforController:(id)view{
UILabel *tempView =(UILabel *)view;
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
context.minimumScaleFactor = 0.8;
float width = tempView.frame.size.width;
width = width * ([[UIScreen mainScreen] bounds].size.width/320);
CGSize size=[tempView.text boundingRectWithSize:CGSizeMake(tempView.frame.size.width, 200)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{ NSFontAttributeName : tempView.font}
context:context].size;
return size.height;
}
it will return you required height for your label and call this method like :
CGRect frame = self.yourlbl.frame;
frame.size.height = [self getHeightforController:self.yourlbl];
self.yourlbl.frame = frame;
That solved..
The UIKit Framework Reference is going to be your best friend. Here is a link to the UILabel Class Reference.
You will need to set the durationLbl.lineBreakMode = UILineBreakModeWordWrap and also set the durationLbl.numberOfLines if needed. 0 means any number of lines and is the default.
update you code :UILabel *titleLbl = (UILabel *)[cell viewWithTag:102]; titleLble.text = str
CGRect frame = titleLbl.frame;
frame.size.height = [self getHeightforController:titleLbl]; titleLbl.frame = frame;'
you are getting this error because titleLbl is object of your cell not your ViewController. and please dont forget to set text before calling that method
please once try this :
lbl.numberOfLines = 0;
lbl.text = str;
[sizeToFit];
without calling that method
I have a little question. I developed an application that automatically fills a data set obtained from a SQLite database. These data are drawn from dynamic way, and between the data I have a frame where I insert labels dynamically. I never know the exact number of labels inside these frame because I take data from different tables of my Database. The problem I have with the labels which texts within them do not fill within the width of the label. I tried to use label.linebreakmode but still makes the break. I post the code:
There I have many objects taken from previous code, like widthFormato and widthImageFormato
if([tipoVino length]!=0){
UILabel *lblFormato = [[UILabel alloc] init];
labelX = ((widthFormato-widthImageFormatos) / 2)+10;
CGRect labelFrame = lblFormato.frame;
labelFrame.size.width = widthImageFormatos;
labelFrame.origin.x = labelX;
labelFrame.origin.y = labelY+25;
lblFormato.frame = labelFrame;
lblFormato.numberOfLines = 0;
lblFormato.lineBreakMode = NSLineBreakByWordWrapping;
[lblFormato setText:[NSString stringWithFormat:#"- %#",tipoVino]];
lblFormato.textColor = [UIColor whiteColor];
labelY = lblFormato.frame.origin.y;
[formatosImageView addSubview:lblFormato];
}
I think that you want to create labels with flexible height (not all have the same size), and must fix the width of formatosImageView.
EDITED FOR iOS7
I was using sizeWithFont that is deprecated in iOS7, I changed it for boundingRectWithSize
Try this:
UILabel *lblFormato = [[UILabel alloc] init];
lblFormato.numberOfLines = 0;
lblFormato.lineBreakMode = NSLineBreakByWordWrapping;
[lblFormato sizeToFit];
NSString *string = [NSString stringWithFormat:#"- %#", tipoVino];
[lblFormato setText: string];
//Calculate the size of the container of the lblFormato
CGSize size = [string boundingRectWithSize:CGSizeMake(widthImageFormatos, 2000) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName: [UIFont fontWithName:#"customFont" size:fontSize]} context:nil];
lblFormato.frame = CGRectMake(labelX, labelY + 25, size.width, size.height);
and you must update the labelY with:
labelY = lblFormato.frame.size.height;
Maybe it helps you.
With regards #spinillos answer, for iOS 7:
NSDictionary *attributes = #{ NSFontAttributeName : [UIFont systemFontOfSize:<#fontSize#>] };
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithString: <#string#>,
attributes:attributes];
CGRect frame = [attributedString boundingRectWithSize:CGSizeMake(<#width#>, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
I am writing an app that uses 3 fonts for 2 language UIs. For hebrew UI with hebrew font all is good, but for the english UI all the labels are getting clipped at the bottom with the english font on iOS6 (on 7 it's good).
I started investigating, and realised that also the line spacing in more than 1 line texts is way too small, and sizeToFit, sizeThatFits: and constrainToSize all gives height that is way too small for that font to fit in the frame.
First I tried to set clipsToBounds:NO which did nothing, the I thought about hardcode enlarging the frame by *1.15 when using the eng font, but than 1) I still have the line spacing problem, and 2) I couldn't get it to work with the UITextFields (For some reason the placeholder and the caret both getting clipped but typed text works fine...) - I tried overriding textRectForBounds: and editingRectForBounds: and return the given bounds with (0,10,0,10) for UIEdgeInsets but no luck...
I thought about editing the font info itself, but A I have no idea how to do it (And couldn't find any info on Google) and B couldn't find any mac app that will do it (Only Windows stuff...). Also - It seems weird that I need to do it because on iOS7 it works perfectly without any need to adjust any frame... (I Tested this on iOS6 & iOS7 simulators AND on iOS6 iPhone 5 device AND iOS7 iPhone5 device...).
Typing text on iOS7:
Placeholder iOS7:
Typing text on iOS6.1:
Placeholder on iOS6.1:
Some other place with UILabel that's clipped:
(This case on button, but happens everywhere)
Again: This only happens on iOS6.1 and not on 7, and my guess is it has something to do with the way the SDK reads the font size in pixels from the font file that has maybe somehow changed from ios6 to 7...
EDIT: This is the work-around tryout I did for the UITextField problem:
- (CGRect) textRectForBounds:(CGRect)bounds {
return [self UIEdgeInsetsInsetRectForTextFieldBounds:bounds];
}
- (CGRect)editingRectForBounds:(CGRect)bounds {
return [self UIEdgeInsetsInsetRectForTextFieldBounds:bounds];
}
- (CGRect)UIEdgeInsetsInsetRectForTextFieldBounds:(CGRect)bounds {
bounds = CGRectMake(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height + 5);
return UIEdgeInsetsInsetRect(bounds, UIEdgeInsetsMake(-2, 10, -2, 10));
}
Note: It's written on a UITextField category. I know it's not the way to go but it does change the insets so I know the code happens and makes an influence, it just doesn't change the farm size for the placeholder as expected.
Didn't work...
Also these are all the tryouts I did for fixing the labels with actually calculating the space needed, as you can see by the comment mark I ended up using a hard-coded-workaround:
(The last paragraph commented is my try to work-around the line spacing problem - also didn't work)
// CGSize maxSize = CGSizeMake(self.frame.size.width, MAXFLOAT);
//// CGSize expectedLabelSize = [self.text sizeWithFont:self.font constrainedToSize:maxSize lineBreakMode:self.lineBreakMode];
//// CGRect newFrame = self.frame;
//// newFrame.size.height = expectedLabelSize.height*2;
//// self.frame = newFrame;
//// [self setClipsToBounds:NO];
//
// CGSize expectedLabelSize = [self.text sizeWithFont:self.font constrainedToSize:maxSize lineBreakMode:NSLineBreakByTruncatingTail];
//
// //adjust the label the the new height.
// CGRect newFrame = self.frame;
// newFrame.size.height = expectedLabelSize.height;
// self.frame = newFrame;
// [self setLineBreakMode:NSLineBreakByWordWrapping];
// [self setNumberOfLines:1];
// [self sizeToFit];
// CGSize maxSize = CGSizeMake(self.frame.size.width, CGFLOAT_MAX);
// CGSize requiredSize = [self sizeThatFits:maxSize];
// self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, requiredSize.width, requiredSize.height);
[self changeFrameHeightTo:self.frame.size.height*1.17];
[self changeFrameYBy:-5];
// NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:self.text];
// NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
// [paragraphStyle setLineSpacing:10];
// [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [self.text length])];
// self.attributedText = attributedString;
**changeFrame... is a category I built for UIView for changing specific values without having to use setFrame:CGRectMake(...*
Why not calculate the text size according to the text and font you use?
CGSize titleSize = [title sizeWithFont:<your font here> constrainedToSize:<button size > lineBreakMode:NSLineBreakByTruncatingTail];
then you can change the size of the placeholder accordingly.
Note, this function is deprecated in ios 7.0.
for ios7 you need to use: boundingRectWithSize:options:attributes:context:
I am using an UITextView that will be expandable by taping a "more" button. The problem is the following:
On iOS6 I use this,
self.DescriptionTextView.text = #"loong string";
if(self.DescriptionTextView.contentSize.height>self.DescriptionTextView.frame.size.height) {
//set up the more button
}
The problem is that on iOS7 the contentSize.height returns a different value (far smaller) than the value it returns on iOS6.
Why is this? How to fix it?
The content size property no longer works as it did on iOS 6. Using sizeToFit as others suggest may or may not work depending on a number of factors.
It didn't work for me, so I use this instead:
- (CGFloat)measureHeightOfUITextView:(UITextView *)textView
{
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
{
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
CGRect frame = textView.bounds;
// Take account of the padding added around the text.
UIEdgeInsets textContainerInsets = textView.textContainerInset;
UIEdgeInsets contentInsets = textView.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString *textToMeasure = textView.text;
if ([textToMeasure hasSuffix:#"\n"])
{
textToMeasure = [NSString stringWithFormat:#"%#-", textView.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary *attributes = #{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle };
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
CGFloat measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
}
else
{
return textView.contentSize.height;
}
}
Try the answer in the following link, layoutIfNeeded should be called before contentSize.
iOS7 UITextView contentsize.height alternative
The answer:
In iOS7, UITextView uses NSLayoutManager to layout text:
// If YES, then the layout manager may perform glyph generation and layout for a given portion of the text, without having glyphs or layout for preceding portions. The default is NO. Turning this setting on will significantly alter which portions of the text will have glyph generation or layout performed when a given generation-causing method is invoked. It also gives significant performance benefits, especially for large documents.
#property(NS_NONATOMIC_IOSONLY) BOOL allowsNonContiguousLayout;
disable allowsNonContiguousLayout to fix contentSize :
textView.layoutManager.allowsNonContiguousLayout = NO;
This link seems to have the answer.
You must use sizeToFit before using contentSize.
Try with following code, Its will be work in both iOS6 and 7, please try it.
CGSize myTextViewSize = [self.myTextView sizeThatFits:CGSizeMake(self.myTextView.frame.size.width, FLT_MAX)];
self.myTextView.height = myTextViewSize.height;
NSLog(#"%f", self.myTextView.height);
Converting a project from iOS5.0 to iOS7 / iOS6 on Xcode 5. The code below is giving a compile time warning:
'sizeWithFont:constrainedToSize:lineBreakMode:'is deprecated: first deprecated in ios 7.0 - Use - boundingRectWithSize:options:attribiutes:context
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == 0)
{
self.lblHidden.frame = CGRectMake(58, 228, 945, 9999);
self.lblHidden.text = detailShareObj.pDesc;
CGSize size = [detailShareObj.pDesc sizeWithFont:self.lblHidden.font constrainedToSize:self.lblHidden.frame.size lineBreakMode:NSLineBreakByWordWrapping];
return 228.0+size.height+20;
}
else if (indexPath.section == 1)
{
NSString *tempPointStr = (self.shortDescArray)[indexPath.row];
self.lblHidden.frame = CGRectMake(58, 0, 945, 9999);
self.lblHidden.text = tempPointStr;
CGSize size = [tempPointStr sizeWithFont:self.lblHidden.font
constrainedToSize:self.lblHidden.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
return 50.0f;
}
I tried some of the suggestion give elsewhere but nothing is up to rescue if some one can help by giving the corrections required in the code will be greatly appreciated.
I wouldn't just mask the deprecated function warning. They deprecated it for a reason. I believe the function was deprecated because that series of NSString+UIKit functions were based on the UIStringDrawing library, which wasn't thread safe. If you tried to run them not on the main thread (like any other UIKit functionality), you'll get unpredictable behaviors. In particular, if you ran the function on multiple threads simultaneously, it'll probably crash your app. This is why in iOS 6, they introduced a the boundingRectWithSize:... method for NSAttributedStrings. This was built on top of the NSStringDrawing libraries and is thread safe.
If you look at the new NSString boundingRectWithSize:... function, it asks for an attributes array in the same manner as a NSAttributeString. If I had to guess, this new NSString function in iOS 7 is merely a wrapper for the NSAttributeString function from iOS 6.
On that note, if you were only supporting iOS 6 and iOS 7, then I would definitely change all of your NSString's sizeWithFont:... to the NSAttributeString's boundingRectWithSize. It'll save you a lot of headache if you happen to have a weird multi-threading corner case! Here's how I converted NSString's sizeWithFont:constrainedToSize::
What used to be:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
CGSize size = [text sizeWithFont:font
constrainedToSize:(CGSize){width, CGFLOAT_MAX}];
Can be replaced with:
NSString *text = ...;
CGFloat width = ...;
UIFont *font = ...;
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:text
attributes:#
{
NSFontAttributeName: font
}];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){width, CGFLOAT_MAX}
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
CGSize size = rect.size;
Please note the documentation mentions:
In iOS 7 and later, this method returns fractional sizes (in the size
component of the returned CGRect); to use a returned size to size
views, you must use raise its value to the nearest higher integer
using the ceil function.
So to pull out the calculated height or width to be used for sizing views, I would use:
CGFloat height = ceilf(size.height);
CGFloat width = ceilf(size.width);
If you want it compatible with both iOS7 and the versions below it, try this one (with ARC):
CGSize size;
if ([tempPointStr respondsToSelector:
#selector(boundingRectWithSize:options:attributes:context:)])
{
NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
paragraphStyle.alignment = NSTextAlignmentLeft;
NSDictionary * attributes = #{NSFontAttributeName : self.lblHidden.font,
NSParagraphStyleAttributeName : paragraphStyle};
size = [tempPointStr boundingRectWithSize:self.lblHidden.frame.size
options:NSStringDrawingUsesFontLeading
|NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil].size;
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
size = [tempPointStr sizeWithFont:self.lblHidden.font
constrainedToSize:self.lblHidden.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
#pragma clang diagnostic pop
}
Note: It's just an example for your else-if case, maybe you need to do some modification depend on what you want it be. ;)
For iOS7, replace:
CGSize size = [tempPointStr sizeWithFont:self.lblHidden.font
constrainedToSize:self.lblHidden.frame.size
lineBreakMode:NSLineBreakByWordWrapping];
With:
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc]init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping; //set the line break mode
NSDictionary *attrDict = [NSDictionary dictionaryWithObjectsAndKeys:self.lblHidden.font, NSFontAttributeName, paragraphStyle, NSParagraphStyleAttributeName, nil];
CGSize size = [tempPointStr boundingRectWithSize:self.lblHidden.frame.size
options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin
attributes:attrDict context:nil].size;
You can use:
UIFont *font = [UIFont boldSystemFontOfSize:16];
CGRect new = [string boundingRectWithSize:CGSizeMake(200, 300)
options:NSStringDrawingUsesFontLeading
attributes:#{NSFontAttributeName: font}
context:nil];
CGSize stringSize= new.size;
If you're targeting iOS 6.0+, you can still use sizeWithFont:constrainedToSize:lineBreakMode:. Just make sure that your project's iOS Deployment Target is set for 6.0, and the compiler won't give you these warnings.
(You can find this by clicking on the blue project tab (usually at the top of the left, project navigator pane) within the "info" section).
If you're only targeting iOS 7.0+, you should use the new method boundingRectWithSize:options:attributes:context.
You can find the Apple docs on this new method here.
The boundingRectWithSize:options:attributes:context has the problem, that it does not calculates the height correctly if the String contains "\n" (line breaks). Therefore this code calculates the size for each line separately for a given width (inWidth):
NSArray *brokenByLines=[string componentsSeparatedByString:#"\n"];
CGFloat height=0.0;
CGFloat maxWidth=0.0;
for (NSString* actString in brokenByLines) {
CGRect tSize=[actString boundingRectWithSize:CGSizeMake(inWidth, 600) options:(NSStringDrawingUsesLineFragmentOrigin | NSLineBreakByWordWrapping) attributes:#{NSFontAttributeName: inFont} context:nil];
if (maxWidth<tSize.size.width) {
maxWidth=tSize.size.width;
}
height+=tSize.size.height;
}
CGSize size= CGSizeMake(ceil(maxWidth), ceil(height));