A "quicky": how can I get the size (width) of a NSString?
I'm trying to see if the string width of a string to see if it is bigger than a given width of screen, case in which I have to "crop" it and append it with "...", getting the usual behavior of a UILabel. string.length won't do the trick since AAAAAAAA and iiiiii have the same length but different sizes (for example).
I'm kind of stuck.
Thanks a lot.
This is a different approach. Find out the minimum size of the text so that it won't wrap to more than one line. If it wraps to over one line, you can find out using the height.
You can use this code:
CGSize maximumSize = CGSizeMake(300, 9999);
NSString *myString = #"This is a long string which wraps";
UIFont *myFont = [UIFont fontWithName:#"Helvetica" size:14];
CGSize myStringSize = [myString sizeWithFont:myFont
constrainedToSize:maximumSize
lineBreakMode:self.myLabel.lineBreakMode];
300 is the width of the screen with a little space for margins. You should substitute your own values for font and size, and for the lineBreakMode if you're not using IB.
Now myStringSize will contain a height which you can check against the height of something you know is only 1 line high (using the same font and size). If it's bigger, you'll need to cut the text. Note that you should add a ... to the string before you check it again (adding the ... might push it over the limit again).
Put this code in a loop to cut the text, then check again for the correct height.
Use below method.
Objective-C
- (CGSize)findHeightForText:(NSString *)text havingWidth:(CGFloat)widthValue andFont:(UIFont *)font {
CGSize size = CGSizeZero;
if (text) {
CGRect frame = [text boundingRectWithSize:CGSizeMake(widthValue, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:font } context:nil];
size = CGSizeMake(frame.size.width, frame.size.height + 1);
}
return size;
}
Swift 3.0
func findHeight(forText text: String, havingWidth widthValue: CGFloat, andFont font: UIFont) -> CGSize {
var size = CGSizeZero
if text {
var frame = text.boundingRect(withSize: CGSize(width: widthValue, height: CGFLOAT_MAX), options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
size = CGSize(width: frame.size.width, height: frame.size.height + 1)
}
return size
}
You need to use Core Graphics to measure the string, as rendered in your specified font and size. See the answers to Measuring the pixel width of a string for a walkthrough.
sizeWithFont:constrainedToSize:lineBreakMode
is deprecated now. Use below code snippet,
UIFont *font=[UIFont fontWithName:#"Arial" size:16.f];
NSString *name = #"APPLE";
CGSize size = [name sizeWithAttributes:#{NSFontAttributeName:font}];
For whatever its worth --- I think the OP takes the wrong way to get there... if the measurement of width only serves to find the place where text should be clipped, and followed by ellipsis --- then OP should be aware of that this facility is implemented in all Text Views in Cocoa...
Pay attention to this enumeration:
typedef NS_ENUM(NSUInteger, NSLineBreakMode) {
NSLineBreakByWordWrapping = 0, // Wrap at word boundaries, default
NSLineBreakByCharWrapping, // Wrap at character boundaries
NSLineBreakByClipping, // Simply clip
NSLineBreakByTruncatingHead, // Truncate at head of line: "...wxyz"
NSLineBreakByTruncatingTail, // Truncate at tail of line: "abcd..."
NSLineBreakByTruncatingMiddle // Truncate middle of line: "ab...yz"
} API_AVAILABLE(macos(10.0), ios(6.0), watchos(2.0), tvos(9.0));
By setting the line breaking mode of your text-field or text view to NSLineBreakByTruncatingTail, you'll achieve what you want, and probably at higher quality, without implementing yourself.
Related
I am trying to create a custom cell which consists a few UILabels.
The first label might take one or more rows, so I need to resize the label according to the number of lines (after setting the number of lines to 0, so multi-line will be enabled).
I have tried setting sizeToFit(), but it changed the alignment and width of my label.
I found this answer
but I don't know how to convert it to C#.
Can anyone point me to an example? (I already tried Googling it off-course)
This is the method from the link:
// UILabel *myLabel;
CGSize labelSize = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap];
CGFloat labelHeight = labelSize.height;
int lines = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap].height/16;
// '16' is font size
var size = myLabel.StringSize("Some really long string", myLabel.Font, myLabel.Frame.Size, UILineBreakMode.CharacterWrap);
var lines = size.Height / myLabel.Font.CapHeight;
I'm making my app transition to iOS 7 and have this method (already modified for iOS 7, using boundingRectWithSize...):
+ (CGSize)messageSize:(NSString*)message {
NSDictionary *attributes = #{NSFontAttributeName : [UIFont fontWithName:#"Futura-Medium" size:13]};
CGRect frame = [message boundingRectWithSize:CGSizeMake([PTSMessagingCell maxTextWidth], CGFLOAT_MAX) options:NSStringDrawingUsesFontLeading attributes:attributes context:nil];
return frame.size;
}
I am getting this appearance:
The message UILabel is being cut. It feels like line spacing is too big. It tried many other answers I found but none of them work.
If someone knows how to help me, I appreciate! ;)
Thanks!
Try changing NSStringDrawingUsesFontLeading as your option to NSStringDrawingUsesLineFragmentOrigin.
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. Starting in iOS 6, the NSAttributedString's NSStringDrawing functions were introduced and they're threadsafe unlike the old NSString+UIKit methods we're used to (eg. sizeWithFont:..., etc), which were UIStringDrawing functions (and act unpredictably when you use them from a non-main thread. 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);
I think you are updating your label frame from either viewDidLoad or viewWillAppear, so it is not working.
if you will update frame of label from viewDidAppear method then you will get updated frame of label.
I am not sure why this is happened, I think it is iOS 7 bug.
Try this
+ (CGSize)messageSize:(NSString*)message {
CGSize nameSize = [message sizeWithFont:[UIFont fontWithName:#"Futura-Medium" size:13]
constrainedToSize:CGSizeMake(maxWidth, maxHeight) lineBreakMode:NSLineBreakByWordWrapping];
NSLog(#"width = %f, height = %f", nameSize.width, nameSize.height);
return nameSize;
}
Is there any method to determine how much of an NSString can be rendered in a given space?
I know about all the NSString sizeWithFont methods (e.g. sizeWithFont:constrainedToSize:lineBreakMode:). If the string is too long to fit though, these don't tell you what portion of the string was able to be rendered.
For example, if I have
NSString *testString = #"The brown dog";
And I call:
[testString sizeWithFont:[UIFont systemFontOfSize:17] constrainedtoSize:CGSizeMake(20, 20) lineBreakMode:UILineBreakModeWordWrap];
I may get back a CGSize = {20,20}. That tells me the string took at least the entire size, but it doesn't tell me if it was over, or how much was able to fit. If only "The brown" was able to fit, I'd like to know that.
Maybe some Core Foundation methods to do this?
One way to tell if it's going to truncate is to provide a very large height in the constrain rect. If the height that comes back is taller than the height of your label, you know it would be truncated. Something like this:
// myLabel is a UILabel*
CGSize labelSize = myLabel.frame.size;
labelSize.height = 9999;
CGSize newSize = [newLabel sizeWithFont:[UIFont systemFontOfSize:17.0]
constrainedToSize:labelSize
lineBreakMode:UILineBreakModeWordWrap];
if( newSize.height > labelSize.height ) {
// Whoops, too big!
}
That will tell you if it would be truncated, but it won't tell you how much of it would be. Only way I can think to do that would be to do this in a loop, removing a word off the end each time until it fits.
I know there is this one:
sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
But since the CGSize always has the same height and doesn't adjust to any shrinked text or whatsoever, the CGSize is not telling how heigh the text is.
Example: Make a UILabel with 320 x 55 points and put a loooooooooooooong text in there. Let the label shrink the text down. Surprise: CGSize.height remains the same height even if the text is so tiny that you need a microscope.
Ok so after banging my head against my macbook pro which is half way broken now, the only think that can help is that nasty actualFontSize. But the font size is in pica I think, it's not really what you get on the screen, isn't it?
When that font size is 10, is my text really 10 points heigh at maximum? Once in a while I tried exactly that, and as soon as the text had a y or some character that extends to below (like that tail of an y does), it is out of bounds and the whole text is bigger than 10 points.
So how would you calculate the real text height for a single line uilabel without getting a long beard and some hospital experience?
Try this code:
CGSize maximumSize = CGSizeMake(300, 9999);
NSString *myString = #"This is a long string which wraps";
UIFont *myFont = [UIFont fontWithName:#"Helvetica" size:14];
CGSize myStringSize = [myString sizeWithFont:myFont
constrainedToSize:maximumSize
lineBreakMode:self.myLabel.lineBreakMode];
from my answer here
It uses a different method, and sets up a very high CGSize at the start (which is then shrunk to fit the string)
Important: As of iOS 7.0 the following method is deprecated.
sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:
Use the below code instead
CGRect frame = [cellText boundingRectWithSize:CGSizeMake(568,320) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:32.0f]} context:nil];
float height = frame.size.height;
Sounds like after you get the actual font size from that function call, you need to call again with that new size:
NSString* yourString = #"SomeString";
float actualSize;
[yourString sizeWithFont:yourFont
minFontSize:minSize
actualFontSize:&actualSize
forWidth:rectWidth
lineBreakMode:breakMode];
CGSize size = [yourString sizeWithFont:[UIFont fontWithName:fontName size:actualSize]];
Also have you set label.numberOfLines = 0; ?
After running this code frame.size will have the height and width of your nsstring exactly..
NSString *text = #"This is my Mac";
textFont = [NSFont fontWithName:#"HelveticaNeue-Medium" size: 80.f];
textColor = [NSColor yellowColor];
NSDictionary *attribs = #{ NSForegroundColorAttributeName:textColor,
NSFontAttributeName: textFont };
CGRect frame = [text boundingRectWithSize:CGSizeMake(0,0) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesDeviceMetrics attributes:attribs context:nil];
When myLabel.adjustsFontSizeToFitWidth = YES, UILabel will adjust the font size automatically in case the text is too long for the label. For example, if my label is just 100px wide, and my text is too long to fit with the current font size, it will shrink down the font size until the text fits into the label.
I need to get the actual displayed font size from UILabel when the font size got shrunk down. For example, let's say my font size was actually 20, but UILabel had to shrink it down to 10. When I ask UILabel for the font and the font size, I get my old font size (20), but not the one that's displayed (10).
I'm not sure if this is entirely accurate, but it should be pretty close, hopefully. It may not take truncated strings into account, or the height of the label, but that's something you might be able to do manually.
The method
- (CGSize)sizeWithFont:(UIFont *)font minFontSize:(CGFloat)minFontSize actualFontSize:(CGFloat *)actualFontSize forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode
will return the text size, and notice that it also has a reference parameter for the actual font size used.
In case anybody still needs the answer.
In iOS9 you can use boundingRectWithSize:options:context: to calculate actual font size. Note that context.minimumScaleFactor should not be 0.0 for scaling to work.
- (CGFloat)adjustedFontSizeForLabel:(UILabel *)label {
NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithAttributedString:label.attributedText];
[text setAttributes:#{NSFontAttributeName:label.font} range:NSMakeRange(0, text.length)];
NSStringDrawingContext *context = [NSStringDrawingContext new];
context.minimumScaleFactor = label.minimumScaleFactor;
[text boundingRectWithSize:label.frame.size options:NSStringDrawingUsesLineFragmentOrigin context:context];
CGFloat adjustedFontSize = label.font.pointSize * context.actualScaleFactor;
return adjustedFontSize;
}
For one-line UILabel works fine this simple solution:
//myLabel - initial label
UILabel *fullSizeLabel = [UILabel new];
fullSizeLabel.font = myLabel.font;
fullSizeLabel.text = myLabel.text;
[fullSizeLabel sizeToFit];
CGFloat actualFontSize = myLabel.font.pointSize * (myLabel.bounds.size.width / fullSizeLabel.bounds.size.width);
//correct, if new font size bigger than initial
actualFontSize = actualFontSize < myLabel.font.pointSize ? actualFontSize : myLabel.font.pointSize;
Swift 5
For one-line UILabel
extension UILabel {
var actualFontSize: CGFloat {
//initial label
let fullSizeLabel = UILabel()
fullSizeLabel.font = self.font
fullSizeLabel.text = self.text
fullSizeLabel.sizeToFit()
var actualFontSize: CGFloat = self.font.pointSize * (self.bounds.size.width / fullSizeLabel.bounds.size.width);
//correct, if new font size bigger than initial
actualFontSize = actualFontSize < self.font.pointSize ? actualFontSize : self.font.pointSize;
return actualFontSize
}
}
Getting the actual font size is then as simple as:
let currentLabelFontSize = myLabel.actualFontSize
UILabel *txtLabel = [[UILabel alloc] initWithFrame:rectMax];
txtLabel.numberOfLines = 1;
txtLabel.font = self.fontMax;
txtLabel.adjustsFontSizeToFitWidth = YES;
txtLabel.minimumScaleFactor = 0.1;
[txtLabel setText:strMax];
UILabel *fullSizeLabel = [UILabel new];
fullSizeLabel.font = txtLabel.font;
fullSizeLabel.text = txtLabel.text;
fullSizeLabel.numberOfLines = 1;
[fullSizeLabel sizeToFit];
CGFloat actualFontSize = txtLabel.font.pointSize * (txtLabel.bounds.size.width / fullSizeLabel.bounds.size.width);
actualFontSize = actualFontSize < txtLabel.font.pointSize ? actualFontSize : txtLabel.font.pointSize;
// the actual font
self.fontMax = [UIFont fontWithName:self.fontMax.fontName size:actualFontSize];
my code works great, part from #Igor