I am currently drawing text on string, using drawInRect, restricted to a size using CGRectSize. It is also centred.
[text drawInRect:CGRectMake(0,0,nWidth,nHeight) withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentCenter];
What I need to find out is the size and offset of the actual drawn text.
Because it is centred, there is a very good chance that there is an X offset before text is drawn.
I've seen the command boundingRectWithSize but I think it may not be supported on IOS?
Get the size of the text,
CGSize size = [text sizeWithFont:font constrainedToSize:CGRectMake(0,0,nWidth,nHeight) lineBreakMode: UILineBreakModeWordWrap];
(See documentation)
Then, since it is centered, the bounds will be
CGRect bounds = CGRectMake((nWidth - size.width)/2, ..., size.width, size.height);
You want the method from the UIKit category on NSString...
- (CGSize)sizeWithFont:(UIFont *)font
which gives you the size needed, and then you can compute your centering (or other) positions.
Related
I need to align the baselines of text in UILabels. What I'm currently doing is I'm aligning the baselines of UILabels containing the text, and when the text font size in two labels is different, this results in aligned UILabels baseline but misaligned text baseline (misaligned by a small margin, but still misaligned). The labels are included in a custom UIView subclass, therefore self refers to the encompassing UIView.
here is the relevant code
[self.mySmallLabel sizeToFit];
[self.myBigLabel sizeToFit];
self.mySmallLabel.frame = CGRectMake(0,
self.bounds.size.height - self.mySmallLabel.bounds.size.height,
self.mySmallLabel.bounds.size.width,
self.mySmallLabel.bounds.size.height);
self.myBigLabel.frame = CGRectMake(self.mySmallLabel.frame.origin.x + self.mySmallLabel.bounds.size.width,
self.bounds.size.height - self.myBigLabel.bounds.size.height,
self.myBigLabel.bounds.size.width,
self.myBigLabel.bounds.size.height);
[self.mySmallLabel sizeToFit];
[self.myBigLabel sizeToFit];
This code results in the aligment in the image linked below.
As you can see, even though the UILabel baselines are aligned, the baselines of the text is misaligned by a small margin. How can I align the baselines of text dynamically (because font sizes might change at runtime)?
I was using this answer in a couple of different places, but the baselines were sometimes a pixel off on Retina displays. The snippet below accounts for the screen’s scale:
[majorLabel sizeToFit];
[minorLabel sizeToFit];
CGRect changedFrame = minorLabel.frame;
changedFrame.origin.x = CGRectGetWidth(majorLabel.frame);
const CGFloat scale = [UIScreen mainScreen].scale;
const CGFloat majorLabelBaselineInSuperView = CGRectGetMaxY(majorLabel.frame) + majorLabel.font.descender;
const CGFloat minorLabelBaselineInOwnView = CGRectGetHeight(minorLabel.frame) + minorLabel.font.descender;
changedFrame.origin.y = ceil((majorLabelBaselineInSuperView - minorLabelBaselineInOwnView) * scale) / scale;
minorLabel.frame = changedFrame;
You can get pixel-perfect baseline alignment for any pair of UILabels by using the UIFont ascender value in a simple calculation. Here's how:
[majorLabel sizeToFit];
[minorLabel sizeToFit];
CGRect changedFrame = minorLabel.frame;
changedFrame.origin.y = ceilf(majorLabel.frame.origin.y + (majorLabel.font.ascender - minorLabel.font.ascender));
minorLabel.frame = changedFrame;
ceilf() is used because the font.ascender values may be fractional.
I've tested this on both retina and non-retina devices, with excellent results. Positioning the two labels relative to each other on the x-axis has been omitted, as your needs may vary. If you need a quick explanation of what the UIFont ascender is (plus other UIFont info) check out this clear, concise article.
After iOS9.
With autolayout, UILabel has an anchor called: lastBaselineAnchor.
For example:
hintLabel.lastBaselineAnchor.constraint(equalTo: titleLabel.lastBaselineAnchor).isActive = true
I was looking to do this myself (just now) and found my answer on an almost identical question. It's not simple solution though, we have to do the math.
I only needed to do it with 2 different labels and I'm doing it in a subclass of UIView.
- (void)layoutSubviews {
[majorLabel sizeToFit];
[minorLabel sizeToFit];
CGRect changedFrame = minorLabel.frame;
changedFrame.origin.x = majorLabel.frame.size.width;
changedFrame.origin.y = (majorLabel.frame.size.height + majorLabel.font.descender) - (minorLabel.frame.size.height + minorLabel.font.descender);
minorLabel.frame = changedFrame;
}
With Autolayouts, its much more easier.
Select the 2 labels you wish to align and goto the Align tool. Select "Bottom Edges"/ "Top Edges" / Baseline
On what factors does the wrapping of the text inside a textview depends. Width of my textview is 160 px and I calculated the width of the incoming text using below mentioned code and it comes out to be 157 px but this text is wrapped in 3 lines... Why so?
CGSize size = [myText sizeWithFont:textViewFont
constrainedToSize:textView.frame.size
lineBreakMode:UILineBreakModeWordWrap];
CGFloat textWidth = size.width;
I thought of dividing width of text with width of the textview to get the number of lines. But calculation returns me 1 whereas I can see 3 lines coming in textview on simulator.
- (CGSize)sizeWithFont:(UIFont *)font
constrainedToSize:(CGSize)size
lineBreakMode:(UILineBreakMode)lineBreakMode
Calculates the size for a string if it would be drawn constrained by the size given as an argument, if the string is too long for the given size constraint it will get truncated to fit.
To get the width of the string if it's drawn on a single line with no constraints use:
- (CGSize)sizeWithFont:(UIFont *)font
see: http://developer.apple.com/library/ios/#documentation/uikit/reference/NSString_UIKit_Additions/Reference/Reference.html
NOTE: As of iOS7
- (CGSize)sizeWithFont:(UIFont *)font
is deprecated, instead use:
- (CGSize)sizeWithAttributes:(NSDictionary *)attrs
Try setting the height of the size you pass into that method to be something huge. Otherwise the returned size will always show the actual height of the text view (assuming the full string does not fit in the given height).
Is it possible to get final font size, after autoadjusting? (property adjustsFontSizeToFitWidth set to YES, and text font size is being shrinked to fit into the label)
I am subclassing drawTextInRect in UILabel to put gradient on the text, but the gradient size needs to be the same, as the size of the font. I am not able to get proper size of the adjusted font...Is it even possible?
//draw gradient
CGContextSaveGState(myContext);
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1, 1, 1, 0.25, // BOTTOM color
1, 1, 1, 0.12 }; // UPPER color
//scale and translate so that text would not be rotated 180 deg wrong
CGContextTranslateCTM(myContext, 0, rect.size.height);
CGContextScaleCTM(myContext, 1.0, -1.0);
//create mask
CGImageRef alphaMask = CGBitmapContextCreateImage(myContext);
CGContextClipToMask(myContext, rect, alphaMask);
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
//gradient should be sized to actual font size. THIS IS THE PROBLEM - EVEN IF FONT IS AUTO ADUJSTED, I AM GETTING THE SAME ORIGINAL FONT SIZE!!!
CGFloat fontCapHeightHalf = (self.font.capHeight/2)+5;
CGRect currentBounds = rect;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds)-fontCapHeightHalf);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds)+fontCapHeightHalf);
CGContextDrawLinearGradient(myContext, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
CGContextRestoreGState(myContext);
You can't get the size directly, but you can calculate it easily enough using these functions:
CGFloat actualFontSize;
[label.text sizeWithFont:label.font
minFontSize:label.minimumFontSize
actualFontSize:&actualFontSize
forWidth:label.bounds.size.width
lineBreakMode:label.lineBreakMode];
CGSize size = [label.text sizeWithFont:label.font minFontSize:10 actualFontSize:&actualFontSize forWidth:200 lineBreakMode:UILineBreakModeTailTruncation];
My answer isn't very helpful for the original question, but I end here every time I search how to get the font size after auto adjust.
First of all, sizeWithFont has been deprecated in iOS 7.0, as we all know, so we have to find another solution.
My solution fits my case, in which I have two labels, one with more constraints and more text than second one.
Here is a step-by-step example:
two labels with same width and different text length:
I want, at run-time, the font size of the main uilabel to be resized and the second one to have the same font size.
decrease the size of both labels, so that the text of the first one fits the frame, in this case 12 for both:
after having set up the position constraints (in my example, the labels stay in the center of the parent view), update the both labels' frames so that they fit the text exactly:
add the Aspect Ratio constraints between width and height to both labels and the width constraint between the main label and the parent view:
add the width constraint from the second label to the main label:
now you can set font size of the labels as you wish:
Here is a screenshot of the Simulator:
The first label is resized according to the screen size and the font size is adjusted consequently. The font size of the second label is the same due to the width constraint and the font's parameters specified in the last image.
This is only an example that aims to showcase the "trick" behind my solution: bind the frames of the labels at an initial font size, so that it will be the same at run-time. I imagine that this technique could be re-adapted to labels with a variable size, just by adding the constraints between labels after having set the text and having resized the frame to fit the text.
This simple solution works fine for one-line UILabel:
//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;
I need to create a custom UILabel to display some dynamic multiline text. The text color is white on a black background. But the background should only be visible right behind the text to simulate the effect of an selected text area.
I started with subclassing UILabel and overriding drawTextInRect to do my own drawings.
- (void) drawTextInRect:(CGRect)rect
{
/* do some custom drawings here */
[super drawTextInRect:rect];
}
So far i could not figure out a way to compute the text-bounds do draw my background into.
Does anybody now how do do this kind of stuff? Thanks a lot.
NSString has some additions in UIKit to allow for calculating the size used to render the string, given various parameters. You can use these methods to calculate the size the UILabel needs to render the string, and then resize the UILabel to precisely this size. Look in the documentation for a method called -sizeWithFont:, all of the other variations are listed there. Make sure you use the right method to match how your UILabel is configured.
There is one caveat here, which is that on iOS 4, there is a bug where these methods actually return a slightly different size than is actually used for drawing (at least for the system font on iPhone 4's [e.g. Helvetica Neue], I don't know if this bug affects any other fonts). Unfortunately the only workaround I know of is to switch to Core Text for all your text rendering, so you may just prefer to live with this bug (if it even affects you) until Apple pushes out a software update. This bug does affect Apple's own applications so there is plenty of precedent for not handling it.
Ok. Now after some input from stackoverflow i am using this code snippet to get the textbounds but it only works fine with single line text.
- (void) drawTextInRect:(CGRect)rect
{
CGFloat lineHeight = [self.text sizeWithFont:self.font].height;
CGSize testSize = CGSizeMake(320, lineHeight * self.numberOfLines);
CGSize textSize = [self.text sizeWithFont:self.font constrainedToSize:testSize lineBreakMode:self.lineBreakMode];
//NSLog(#"drawTextInRect lineHeight %f, width %f x height %f", lineHeight, textSize.width, textSize.height);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 1.0);
CGContextAddRect(context, CGRectMake(0, 0, textSize.width, textSize.height));
CGContextFillPath(context);
CGContextRestoreGState(context);
[super drawTextInRect:rect];
}
If i have multiline text i still need some code to compute the path-information of the text and not just the bounding rect so that i can use CGContextDrawPath to draw my background. Any thoughts on that? Thanks.
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.