Get UILabel font pointsize after minimumFontSize - ios

I have a UILabel with a font of point size 17. If I call label.font.pointSize I get 17, which is all good. BBUUUUTTT I also have a minimumfontsize set to 8, now if I cram some text in the label which causes the point size to shrink and then call label.font.pointsize I still get 17 even though I know the point size is smaller
Any ideas how to get the true point size after system has resized the font?

I don't know of an API to get the current point size of the UILabel when it is scaling your content down. You can try to approximate "scaling factor" using sizeWithFont APIs.
Just an idea:
// Get the size of the text with no scaling (one line)
CGSize sizeOneLine = [label.text sizeWithFont:label.font];
// Get the size of the text enforcing the scaling based on label width
CGSize sizeOneLineConstrained = [label.text sizeWithFont:label.font constrainedToSize:label.frame.size];
// Approximate scaling factor
CGFloat approxScaleFactor = sizeOneLineConstrained.width / sizeOneLine.width;
// Approximate new point size
CGFloat approxScaledPointSize = approxScaleFactor * label.font.pointSize;

As savner pointed out in the comments, this is a duplication question. The cleanest solution is found here: How to get UILabel (UITextView) auto adjusted font size?. However, Sanjit's solution also works! Thanks Everybody!
CGFloat actualFontSize;
[label.text sizeWithFont:label.font
minFontSize:label.minimumFontSize
actualFontSize:&actualFontSize
forWidth:label.bounds.size.width
lineBreakMode:label.lineBreakMode];

Swift 4 and iOS 7+ version (sizeWithFont is now deprecated) of #Sanjit Saluja's answer:
// Get the size of the text with no scaling (one line)
let sizeOneLine: CGSize = label.text!.size(withAttributes: [NSAttributedStringKey.font: label.font])
// Get the size of the text enforcing the scaling based on label width
let sizeOneLineConstrained: CGSize = label.text!.boundingRect(with: label.frame.size, options: .usesLineFragmentOrigin, attributes: [NSAttributedStringKey.font: label.font], context: nil).size
// Approximate scaling factor
let approxScaleFactor: CGFloat = sizeOneLineConstrained.width / sizeOneLine.width
// Approximate new point size
let approxScaledPointSize: CGFloat = approxScaleFactor * label.font.pointSize

Related

dynamic textview with arabic text entry

I am using a dynamic textview.
func textViewDidChange(_ textView: UITextView) {
let str = textView.text! as NSString
let size = str.size(attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 20.0)])
textView.frame = CGRect(origin: textView.frame.origin, size: size)
}
This is the code used for dynamic change in height and width. This works well while using "English". The problem is when i change language to "arabic", text view has to increase width towards the left, but it still increasing the width towards the right.
First check the iPhone screen co-ordinate system below.
I am saying if you increase the width it will always increasing the width towards the right in iPhone. So set your initial origin of the UITextView at the right most and then decrease origin's X Co-ordinate by the amount 'size' you calculate and then set the new origin and size values to the UITextView frame. Hope u understand.
For example :
lets say your initial UITextView origin is at (300,30) and size is (30,30)
now you enter some text Arabic text, and you calculate the size of the text as 100.
Now your new origin's X will be 300 - 100 = 200
so newOrigin = (200,30)
and newSize = (100,30)
now set the UITextView frame as- textView.frame = CGRect(origin: newOrigin, size: newSize)

Calculate width required to fit text for a multiline UILabel?

I am trying to expand the width of a 2 line UILabel to accommodate a varying amount of text. To do this I am attempting to calculate the width required and then setting it to a width constraint I have set on the label.
However, everything I have done ignores the height/word wrapping, and returns the width required for a single line of text (not 2 lines). Any ideas on how i can find the width needed if 2 or more lines are used in the label?
I have tried using the following methods to find the width:
1.Using boundingRectWithSize to determine the appropriate size for a fixed height (height of 2 lines)
CGSize maxSize = CGSizeMake(MAXFLOAT, fixedHeight);
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = NSLineBreakByWordWrapping;
NSDictionary *attributes = #{NSFontAttributeName: self.font, NSParagraphStyleAttributeName: paragraphStyle};
CGRect rect = [self.text boundingRectWithSize: maxSize
options: (NSStringDrawingUsesLineFragmentOrigin)
attributes: attributes
context: nil];
Using the sizeThatFits method to determine the appropriate size.
Thanks!
Make sure fixedHeight you are passing is sufficient enough to accommodate multiple lines. To be safe you can use following
CGSize maxSize = CGSizeMake(300, CGFLOAT_MAX); //where 300 is the width of label
Setting above height will make sure it takes into consideration multiple lines as width is limited. In previous case you were setting width to MAXFLOAT thus width was big enough to accommodate entire content in single line.
It might be silly, but make sure that 'numberOfLines' property of the UILabel is set to 0, so that the size calculation methods can use multiline options.

Positioning a UILabel directly beneath another UILabel

I have an addition to NSString which automatically resizes a UILabel depending on the text that's being read into it (I have a simple app showing quotations, so some are a few words, some a couple sentences). Below that quote label, I also have an author label, which (oddly enough) has the author of the quote in it.
I'm trying to position that author label directly beneath the quote label (as in, its y coordinate would be the quote label's y coordinate plus the quote label's height. What I'm seeing is some space being placed between the two labels, that depending on the length of the quote, changes size. Smaller quotes have more space, while longer quotes have less space. Here's a quick diagram of what I'm seeing:
Note the gap between the red and blue boxes (which I've set up using layer.borderColor/borderWidth so I can see them in the app), is larger the shorter the quote is.
If anyone can sift through the code below and help point me towards exactly what's causing the discrepancy, I'd be really grateful. From what I can see, the author label should always be 35 pixels beneath the quote label's y + height value.
Just to confirm: everything is hooked up correctly in Interface Builder, etc. The content of the quote's getting in there fine, everything else works, so it's hooked up, that isn't the issue.
To clarify, my question is: Why is the gap between the labels changing dependant on the quote's length, and how can I get a stable, settable gap of 35 pixels correctly?
Here's the code I'm using to position the labels:
// Fill and format Quote Details
_quoteLabel.text = [NSString stringWithFormat:#"\"%#\"", _selectedQuote.quote];
_authorLabel.text = _selectedQuote.author;
[_quoteLabel setFont: [UIFont fontWithName: kScriptFont size: 28.0f]];
[_authorLabel setFont: [UIFont fontWithName: kScriptFontAuthor size: 30.0f]];
// Automatically resize the label, then center it again.
[_quoteLabel sizeToFitMultipleLines];
[_quoteLabel setFrame: CGRectMake(11, 11, 298, _quoteLabel.frame.size.height)];
// Position the author label below the quote label, however high it is.
[_authorLabel setFrame: CGRectMake(11, 11 + _quoteLabel.frame.size.height + 35, _authorLabel.frame.size.width, _authorLabel.frame.size.height)];
Here's my custom method for sizeToFitMultipleLines:
- (void) sizeToFitMultipleLines
{
if (self.adjustsFontSizeToFitWidth) {
CGFloat adjustedFontSize = [self.text fontSizeWithFont: self.font constrainedToSize: self.frame.size minimumFontSize: self.minimumScaleFactor];
self.font = [self.font fontWithSize: adjustedFontSize];
}
[self sizeToFit];
}
And here's my fontSizeWithFont:constrainedToSize:minimumFontSize: method:
- (CGFloat) fontSizeWithFont: (UIFont *) font constrainedToSize: (CGSize) size minimumFontSize: (CGFloat) minimumFontSize
{
CGFloat fontSize = [font pointSize];
CGFloat height = [self sizeWithFont: font constrainedToSize: CGSizeMake(size.width, FLT_MAX) lineBreakMode: NSLineBreakByWordWrapping].height;
UIFont *newFont = font;
// Reduce font size while too large, break if no height (empty string)
while (height > size.height && height != 0 && fontSize > minimumFontSize) {
fontSize--;
newFont = [UIFont fontWithName: font.fontName size: fontSize];
height = [self sizeWithFont: newFont constrainedToSize: CGSizeMake(size.width, FLT_MAX) lineBreakMode: NSLineBreakByWordWrapping].height;
};
// Loop through words in string and resize to fit
for (NSString *word in [self componentsSeparatedByCharactersInSet: [NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
CGFloat width = [word sizeWithFont: newFont].width;
while (width > size.width && width != 0 && fontSize > minimumFontSize) {
fontSize--;
newFont = [UIFont fontWithName: font.fontName size: fontSize];
width = [word sizeWithFont: newFont].width;
}
}
return fontSize;
}
After you called size to fit on both labels, calculate the distance between their frames and change them accordingly:
[quoteLabel sizeToFit];
[authorLabel sizeToFit];
float distance = authorLabel.frame.origin.y - quoteLabel.frame.size.height;
float difference = distance - 35;
authorLabel.frame = CGRectMake(authorLabel.frame.origin.x,(authorLabel.frame.origin.y - difference),authorLabel.frame.size.width,authorLabel.frame.size.height);
The reason the gap changes is that the quote label frame changes its height dependent on its content when you call sizeToFit.
UPDATE
Given the recent developments in the comments, I think you have 3 possibilities:
resize the whitespace instead of only the words, so that the string
actually fits in the frame correctly
somehow access the CTFramesetter of UILabel to see what the actual
frame, when all is said and done, amounts to
make your own UIView subclass that handles Core Text drawing in its
draw rect method (should be easy in your case), since after all you
are trying to give to UILabel a behavior that it's not meant for
It probably is moving where you want it, but then an auto-layout constraint or a spring/strut is moving it afterwards.
EDIT:
My first thought (which I ruled out because you said that the box around the words was the label frame. In later comments, you say that this is not an actual screen shot, but just a representation of it, so it could still be correct) was that you are doing this wrong:
[_quoteLabel sizeToFitMultipleLines];
[_quoteLabel setFrame: CGRectMake(11, 11, 298, _quoteLabel.frame.size.height)];
In the first line, you are sizing the text to fit in whatever the current width of the label might be, and then you turn around in the second line and change the width of the label. So most likely, what is happening is that you are sizing the label for some smaller width, which makes it tall. You then make the label wider than it was before and the text expands to fit the wider label, leaving a blank area beneath the actual text, although the frame has not changed. This makes the space betwee the labels exactly 35 as you want, however the top label's text does not go all of the way to the bottom of its frame so the white space is more than you want. Basically, you have this:
*************
* text text *
* text text *
* *
* *
* *
*************
*************
* text text *
*************
If this is the case, then you would fix it by setting the width first, like this:
// You could put anything for the 200 height since you will be changing it in the next line anyway.
[_quoteLabel setFrame: CGRectMake(11, 11, 298, 200];
[_quoteLabel sizeToFitMultipleLines];
I ended up solving the problem by using a single UILabel, and CoreText with an NSAttributedString. Kind of a cop-out, but it works.

Dealing with points or pixels when using UIFont

I was trying to position a string in the vertical center of a CGRect in my drawRect:(CGRect) method.
The CGRect has this size:
CGRect rect = CGRectMake(0.0f, 0.0f, rectWidth, rectHeight);
To draw the string so that it is centered I tried this first:
CGFloat diffHeightRectAndFont = rectHeight - font.capHeight;
[str drawAtPoint:CGPointMake(0.0f, diffHeightRectAndFont * 0.5f) withFont:font];
This I first assumed would find the difference in height between my rect and font, then offsetting the font by half this height should give a y position in the "sweet spot".
However, the capHeight is in points and the rectHeight in pixels, so this solution kind of worked for fonts in the size range 12-15. After that the difference started to position the string outside the rect.
I went over this a few times and the only way to consistently position the string correctly turned out to be this 'hack', which is valid but does not do wonders for the readability of the code:
CGFloat diffHeightRectAndFont = rectHeight - [[NSString stringWithString:#"F"] sizeWithFont:font].height;
Is there a more direct way of obtaining the pixel height of a capital letter using a specific font?
Thank you in advance:)
I know its a bit late. but nevertheless my answer my help someone else.
NSString has a sizeWithFont: method (documented here) that I think can be used for this. It returns a CGSize structure, so you could do something similar to the following to find the height of the text inside your label.
CGSize textSize = [[label text] sizeWithFont:[label font]];
CGFloat heightOfStringWithSpecifiedFont = textSize.height;
UILabel has a font property that you can use to dynamically get the font details for your label as I'm doing above.
Hope this helps :)

Calculate max font size that fits in a rect?

I'm attempting to find the maximum font size that will fit in a given rect for a given string. The goal of the algorithm is to fill as much of the rect as possible with as large of a font as possible. My approach -- which is modified from one I found online -- does a fair job, but it often doesn't fill the entire rect. I'd love to see some collaboration on how to improve this algorithm so that everyone might benefit from it:
-(float) maxFontSizeThatFitsForString:(NSString*)_string
inRect:(CGRect)rect
withFont:(NSString *)fontName
onDevice:(int)device
{
// this is the maximum size font that will fit on the device
float _fontSize = maxFontSize;
float widthTweak;
// how much to change the font each iteration. smaller
// numbers will come closer to an exact match at the
// expense of increasing the number of iterations.
float fontDelta = 2.0;
// sometimes sizeWithFont will break up a word
// if the tweak is not applied. also note that
// this should probably take into account the
// font being used -- some fonts work better
// than others using sizeWithFont.
if(device == IPAD)
widthTweak = 0.2;
else
widthTweak = 0.2;
CGSize tallerSize =
CGSizeMake(rect.size.width-(rect.size.width*widthTweak), 100000);
CGSize stringSize =
[_string sizeWithFont:[UIFont fontWithName:fontName size:_fontSize]
constrainedToSize:tallerSize];
while (stringSize.height >= rect.size.height)
{
_fontSize -= fontDelta;
stringSize = [_string sizeWithFont:[UIFont fontWithName:fontName
size:_fontSize]
constrainedToSize:tallerSize];
}
return _fontSize;
}
Use the following method to calculate the font which can fit, for a given rect and string.
You can change the font to the one which you require.
Also, If required you can add a default font height;
Method is self explanatory.
-(UIFont*) getFontTofitInRect:(CGRect) rect forText:(NSString*) text {
CGFloat baseFont=0;
UIFont *myFont=[UIFont systemFontOfSize:baseFont];
CGSize fSize=[text sizeWithFont:myFont];
CGFloat step=0.1f;
BOOL stop=NO;
CGFloat previousH;
while (!stop) {
myFont=[UIFont systemFontOfSize:baseFont+step ];
fSize=[text sizeWithFont:myFont constrainedToSize:rect.size lineBreakMode:UILineBreakModeWordWrap];
if(fSize.height+myFont.lineHeight>rect.size.height){
myFont=[UIFont systemFontOfSize:previousH];
fSize=CGSizeMake(fSize.width, previousH);
stop=YES;
}else {
previousH=baseFont+step;
}
step++;
}
return myFont;
}
There is no need to waste time doing loops. First, measure the text width and height at the max and min font point settings. Depending on whichever is more restrictive, width or height, use the following math:
If width is more restrictive (i.e., maxPointWidth / rectWidth > maxPointHeight / rectHeight) use:
pointSize = minPointSize + rectWidth * [(maxPointSize - minPointSize) / (maxPointWidth - minPointWidth)]
Else, if height is more restrictive use:
pointSize = minPointSize + rectHeight * [(maxPointSize - minPointSize) / (maxPointHeight - minPointHeight)]
It may be impossible to fill a rectangle completely.
Say at a certain font size you have two lines of text, both filling the screen horizontally, but vertically you have almost but not quite three lines of space.
If you increase the font size just a tiny bit, then the lines don't fit anymore, so you need three lines, but three lines don't fit vertically.
So you have no choice but to live with the vertical gap.

Resources