How to justify text perfectly on iOS 7 - ios

I'm trying to horizontally align text bits with different size and fonts.
Here is the result I'd like (made with scribus):
You can see that text is horizontally align perfectly (to the pixel).
I tried implementing this with a basic UITextView, and justifying the text. Here is what I got:
You can see that the text is not horizontally aligned to the pixel.
I tried centering and it's still not good enough:
If things were not hard enough, the first and third line are dynamic text... so their size changes.
I had a look at textkit but didn't find anything conclusive...
My leads are:
find best font size for text given the width.
space glyphs evenly so that the text bounding box is equal to the desired width.
but I have no clue how to do that.
Any leads ? thanks !

Have you tried NSString's boundingRectWithSize:options:attributes:context:?
CGRect rect = [string boundingRectWithSize:CGSizeMake(width, height)
options:NSStringDrawingUsesFontLeading
attributes:attributes
context:nil];
If you are also supporting iOS 6, use -[NSString sizeWithFont:constrainedToSize:lineBreakMode:]
CGSize textSize = [string sizeWithFont:uifont
constrainedToSize:maximumCGSize
lineBreakMode:lineBreakMode];
The sizeWithFont:constrainedToSize:lineBreakMode: method has been deprecated as of iOS 7.
Using one of these methods, you can find the line that is the widest and match the others to it. In your case of dynamic text, I would suggest against using UITextView to ensure text is correctly justified.

Related

boundingRectWithSize sometimes returns height that is about 1 line too tall

I have looked at several Stackoverflow questions where people have problems with boundingRectWithSize, but none of them are quite my problem.
The following code often computes a height that is 1 line of text too tall.
CGFloat height = [label.attributedText
boundingRectWithSize:CGSizeMake(label.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil].size.height;
For example, the string Map Type: USGS Topo is about half the width of my view and should be 1-line tall (~18 pixels), but ends up having a 2-line height (~36 pixels):
I also tried calculating the height using the NSString instead of NSAttributedString, but the same thing happens:
CGFloat height = [[label.attributedText string]
boundingRectWithSize:CGSizeMake(label.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:TITLE_FONT}
context:nil].size.height;
You can see in this screenshot that the bottom 3 labels end up sized correctly, but not the top two. I turned on color blended labels in the iOS simulator so the subviews would be colored to illustrate:
You probably have a trailing newline in your string. Try trimming it with whitespaceAndNewlineCharacterSet.
I have had something similar happen to me awhile ago, though it was not with an attributed string. When it happened to me the lable's frame wasn't fully set yet and so at the time of the calculation the width was narrow enough that it had to wrap to the next line. Check the frame for your label when you do this calculation and make sure it is actually as wide as you think it is.

Multiline fit-to width UILabel supporting max font size per line and slick animations

First time posting here. I am researching how to recreate this super slick text container featured in the Spark Post app from adobe.
IMAGE:
https://i.stack.imgur.com/6auPa.png
VIDEO:
https://www.youtube.com/watch?v=SfvkreOo3V0&feature=youtu.be
I have already looked into other answers diving into how to calculate the max font size for a container by iterating on different sizes until reaching the better fit.
But if you look carefully this is different:
The text is being tokenized into words
The words somehow behave like containers, UIViews perhaps?
The words animate to it's new position gracefully
The calculation for max font size occurs in real time!
Activating the special mode every line expands to it max font size
It always preservers the given container size
I would love to hear some expert opinions about this.
I think you can resolve it by following steps
Break the text into words.
Find the font size of each Word based on rectangle width and height and
number of character.
Create label for each word based on calculating height.
if you do this every time rectangle width height change I think it will look like animation.
Hey thanks for helping out.
As described above the trivial part of the solution is to segment the text in words and put it into boxes, for that you can use this:
-(CGSize)sizeForText:(NSString*)text
withHyperLabel:(TTHyperLabel*)hyperLabel{
CGSize maxSize=CGSizeMake(MAXFLOAT, MAXFLOAT);
NSDictionary *attributes=#{
NSFontAttributeName: [UIFont fontWithName:hyperLabel.fontName size:100],
};
CGRect textRect = [text boundingRectWithSize:maxSize
options:0
attributes:attributes
context:nil];
return textRect.size; }
The tricky part is the "sorting" algorihtm. I ended up writing my own "fluid text container" algorithm and it seems to work pretty close to the spark's version
.
https://www.youtube.com/watch?v=yM13PSLW6Fg&feature=youtu.be
The gist is to consider the inner boxes as a fluid being poured into a container. If there's any interest I may open source it later. Let me know!
Thanks!

iOS: Modify a font height, width, and spacing independently when creating PDF

In an iOS app I need to use a specific font but each character needs to be taller, thinner, and the spacing closed up to fit correctly. Is it possible to stretch/squish a font programmatically?
When you're adding text to a PDF file there are multiple ways to influence how the text is going to appear. The most generic way (and the way that might actually be sufficient for you) is to scale the text matrix:
void CGContextSetTextMatrix ( CGContextRef c, CGAffineTransform t );
As mentioned in the comment by #mkl, you can provide a matrix that will scale up in the Y direction while scaling down in the X direction. The effect will be that the letters are stretched out vertically and squished horizontally.
Normally I would expect you don't have to touch the spacing in that case, as spacing will be "squished" together just as the other characters.
Just in case that isn't sufficient, PDF actually does provide a way to change the spacing between characters too:
void CGContextSetCharacterSpacing ( CGContextRef context, CGFloat spacing );
While Apple's description talks about "additional space" to add between characters, the PDF specification and I suspect Apple's implementation as a result allows the spacing value to be both positive and negative. A negative value would have the effect of moving the characters closer together.
Seems like the best option would be to create your own custom font.
You are able to change the kerning of your font (the space between the letters) and the thickness/thinness of the font, however you probably aren't able to edit the height of the font, unless you edit the bounding box the font is inside of to scale the letters differently.
You might also want to consider using a different font...or if you're REALLY hardcore you can edit the font yourself using photoshop/illustrator.

Fonts not fitting properly in UILabel - Ascender or Descender cropped

I've done extensive searching/reading/testing and cannot find a solution to this problem.
I've tried since iOS 4.3 and it's still not resolved in iOS7.
The problem is this: Fonts at large sizes can have their Ascenders or Descenders cropped in a UILabel.
Here's a screenshot directly from the Xcode 5.1 UI (no code at all!) showing the problem - font size 300 points:
As you can see, even a simple font like Helvetica Neue (bold or not) has it's Descender cropped. (You're seeing UIViewController > UIView > UILabel)
If you try this and then change the point size you'll see the font scale down, and eventually the Descender will not be cropped. Here it is again at 160 points:
Notice also that some fonts do not get cropped and others do - try Noteworthy, or Papyrus, or Savoye LET - all of which are standard iOS & fonts....
I'm talking about Height here - I know I can use adjustsFontSizeToFitWidth=YES to see the entire length, and I also know I can use sizeToFit, however neither guarantees no cropping of the Ascender/Descender.
Notice also that calculating the height using Ascender/Descender values does not help as the main issue is that the font is not centered vertically within the label when it is drawn. (If it were, it would be a simple calculation.)
So here is the question: How can I show a font as tall as possible and be assured that the Ascender/Descender is not cropped regardless of the font used?
EDIT:
I re-read my question and realized I did not ask it properly - I'm able to resize the label to fit the font - that's not the problem. Here's the revised question:
How can I draw text in a UILabel as large as possible and be assured that it is centered vertically, with no cropping of the Ascender or Descender?
I can easily figure out the overall height of the text, and once I know it will fit, how can draw it in the UILabel vertically centered?
For Example: In the first screenshot, the text "Tg" is cropped, but it is easily short enough to fit vertically in the label. In fact, it could be even larger and still fit if it were properly centered. But I know of no way to center it vertically...
The size of the label can be sized according the length of the string, the font attribute used and the size of the font. I use this method a lot and works great for such requirements -
NSString *textWithinLabel = #"Whatever you like, passed from where ever";
CGSize maximumLabelSize = CGSizeMake(300, 1000); //Place your maximum sizes here
//Here I've used Helvetica, though you can pass any font name or font size here to try out
NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:[[UIFont fontWithName:#"Helvetica" size:15] forKey: NSFontAttributeName];
CGSize newExpectedLabelSize = [textWithinLabel boundingRectWithSize:maximumLabelSize options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin attributes:stringAttributes context:nil].size;
CGRect frame = self.yourLabel.frame;
frame.size.height = newExpectedLabelSize.height;
self.yourLabel.frame = frame;
This example will change the height of the label, though you can use it to change width too etc.
The stringAttributes here are used to calculate the size, not to set the attributes. So for example, if your label is using 14pts and you calculate the height for 30pts, it won't change the height of the font, it will only increase the size of the label to accommodate the larger font size. If you want this method to also change the font attributes, you would need to add the appropriate code at the bottom of the method - self.yourLabel.text.font = ... etc.
I hope this answers your question,
Thanks, Jim.
I tried this and it solved my problem. Essentially, the height of the letter is Ascent+Descent. So that's all the space the label needs vertically.
1. [commentLabel sizeToFit]; //To trim out the unwanted area from the label
2. [commentLabel setFrame:CGRectMake(commentLabel.frame.origin.x, commentLabel.frame.origin.y + ABS(commentLabel.font.descender), commentLabel.frame.size.width, commentLabel.font.ascender + ABS(commentLabel.font.descender))];
//The frame adjustment in **(2)**moves the label down by commentLabel.font.descender because the label by default is aligned based on their actual bottom line instead of the actual line we use on notebooks, where the descender hangs down from the line. In case of a label the bottom line is the lower tip of the descender.

Ignore Ascender and Descender when centering UILabel vertically?

I’m using AutoLayout to position some labels in the vertical centre of a cell. The text is in all-caps, but the UILabel in question, even when sizeToFit is applied, leaves space below the text, which looks a lot like it would be for the tails on letters such as a lower case y, p, and q. Since I’m centring vertically, this is causing an offset and meaning the text appears a few pixels higher than it should do.
Another question may be: can I have a font intelligently adjust its vertical centre dependant on whether it contains any characters which use the ascender or descender?
For instance, the string “abbaba” doesn’t need the descender, whereas the string “oyyoyo” doesn’t need the ascender. Strings in all-caps also never need the descender. If I vertically center “oyyoyoyo” it’ll appear too low.
Thanks, Abhinit, for your answer.
I was also looking for this so I would like to post here the exact constraints you need to apply to align texts to your liking.
This image from Wikipedia shows the different size sections of a font.
So, there are many ways to align a label depending on whether you want to align to the ascender height, the cap height, the x-height, baseline or descender height.
Let's say you have a label containing text in caps like "HELLO" and you want to align with viewAbove to cap height and align with viewBelow to baseline.
You would do:
let font = label.font
let ascenderDelta = font.ascender - font.capHeight
LayoutHelper()
.addViews([
"label":label, "viewAbove":viewAbove, "viewBelow":viewBelow
])
.withMetrics(["ascenderDelta":ascenderDelta])
.addConstraints([
// -- Here the constraints to align to cap height --
"X:label.top == viewAbove.bottom - ascenderDelta",
"X:label.baseline == viewBelow.top",
...other constraints...
])
Note: in the example I'm using my utility class LayoutHelper, but I hope the idea is clear.
About an "auto-aligning" label:
I will think about making an "intelligent" label that adjusts to the appropriate line depending on whether it contains descenders, ascenders, caps, etc.
You could do it using negative insets in drawTextInRect(like here but, for example, using insets = {-ascenderDelta, 0, font.descender, 0}). But that would crop any ascenders/descenders in case you had. I would prefer to align to caps without cropping any possible ascender.
You can use the 'capHeight' and 'xHeight' properties on UIFont to get the correct height and use that to size the UILabel.
Of course this assumes that you know for sure if a string would be lowercase or uppercase only. If not, then you can override setText on a UILabel and check every time the function gets called.
I would also think of looking deeper into CoreText and implementing something like this http://www.zsiegel.com/2012/10/23/Core-Text-Calculating-line-heights/
I had the same problem and solved it by subclassing UILabel and changing its draw method:
- (void)drawRect:(CGRect)rect
{
CGRect capCenteredRect = CGRectMake(rect.origin.x, (self.font.leading-self.font.capHeight)*0.5f, rect.size.width, rect.size.height);
[super drawTextInRect:capCenteredRect];
}
In my case I needed to center to caps because the vertical centering of the UILabel is always some pixels off.
If you need to center vertically to lower case letters with descender you can change the rect to:
CGRectMake(rect.origin.x, (self.font.leading-self.font.xHeight)*0.5f+self.font.descender, rect.size.width, rect.size.height)

Resources