First of all, there are many questions on StackOverflow, but none that fully answer this question.
The problem is mainly, but most likely not limited to, Thai and Arabic diacritics when rendered with a custom Latin-only font, using the text property of a UILabel. Which is also intrinsically sized in an auto-layout. I've already done everything Apple suggests, playing with the settings mentioned in their documentation, WWDC videos, as well as questions on StackOverflow (e.g. clipsToBounds = NO, etc.). Keep in mind, only my custom font setup clips in my scenario, not the iOS system font (.SF-UIDisplay), and not even the iOS system provided Helvetica or Helvetic Neue. The custom font has been checked and rechecked, and at this point the conclusion, iOS is the anomaly across all platforms, even macOS. To be even clearer, the same clipping behavior as the custom font can be seen with SF Pro, a font provided by Apple themselves here: https://developer.apple.com/fonts/
This question is about the most proper, least intrusive, and most complete way to do what is necessary to not clip diacritics. Meaning, how would you do this, ideally, from scratch.
All of my font research and test runs have led all those involved in this problem to believe that Apple has implemented special treatment specifically for their system fonts in UILabel, to avoid diacritic clipping. So making that an assumption, I'm also assuming the font is ok, and I'm looking for solutions that do not involve editing the font.
In my tries to use the font, the first thing to go wrong was vertical clipping of the ascender diacritics of Thai glyphs:
นื้ทั้มูHello
This means the glyphs of the font Thonburi when they cascade from the custom Latin-only font. The fix from this point, was to use a custom font only for Thai without any Latin characters, so it could be defined as the primary font, and cascade to the previously mentioned Latin-only custom font. After all this, the custom Thai font still has horizontal clipping issues on diacritics that come at the end of the text:
Worldฟล์
So now I am at a loss for anything further that font management puppetry can do (though still open to suggestions), and I am moving on to more code-centric fixes. I've seen quite a few questions and answers mentioning subclassing UILabel, but I'd like to know what this would look like that could accomplish what I've described.
I'd also like to know if just opting out of UILabel would be an option for anyone. Meaning would writing something from the ground up with TextKit be worth it to avoid all these bugs that seem to only plague iOS, and specifically UILabel.
At first I thought this was a problem with the framework but it's not, it's just a strict enforcement of a font's metrics. And in probably everything but web/app development, fonts are not rendered so strictly, which is why this problem rarely comes up. Fonts have a number of metrics that tell the program rendering it onto the screen how to render it, most importantly how to handle padding. And UILabel (and UITextField, and likely others) applies these metrics strictly. And the problem for us is that some fonts are very decorative and are often too thick or oblique to fit perfectly into the square canvas that each character must fit into (this is especially the case with accents, like umlauts). This isn't a problem outside of web/app development because when a character doesn't fit into its canvas, like a very thick, wide, and oblique W, the program just shows it anyway, and that's why a low-hanging g might spill into the line below it. But if that g was rendered in a single-line UILabel, because of how strict the font-metric enforcement is in iOS, that low-handing g is clipped.
Subclassing UILabel (in the case of UILabel) and overriding its intrinsicContentSize to add some extra padding is not a good idea, on further research. For one, it's kind of hacky, but more importantly, it produces constraint warnings in the debugger. The true fix, and the only acceptable fix AFAIK, is to edit the font's metrics.
Download a program like Glyphs (https://glyphsapp.com/), open the font, open the Font's Info, and in the Masters tab, give the font the proper ascender and descender values. To best understand how these values work, open the San Francisco font in the program and see how Apple did it (it's the font they made specifically for macOS and iOS development). As a side note, if you use this app, when you're editing the font's info, go into the Features tab as well, delete all of the features (using the minus icon in the lower left) and hit Update to let the program manage the font's features for you.
The last hurdle is clipping at the leading edge (not the top and bottom) which the ascender and descender metrics don't address. You can use the Glyphs program to edit the canvas size of individual characters to make sure they all fit but that changes the complexion of the font because it changes the character spacing too noticeably. For this problem, I think the best solution is to simply use attributed strings for your labels and text fields. And that's because attributed strings let you safely edit padding without hacking into intrinsic sizes. An example:
someLabel.attributedText = NSAttributedString(string: "Done", attributes: [NSAttributedString.Key.font: UIFont.blackItalic(size: 26), NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle.kItalicCenter])
For convenience, I extended NSMutableParagraphStyle since I use this font all over:
extension NSMutableParagraphStyle {
static var kItalicCenter: NSMutableParagraphStyle {
let s = NSMutableParagraphStyle()
s.alignment = .center
s.firstLineHeadIndent = 2
s.headIndent = 2
return s
}
}
This label will push the font forward a couple of points to prevent clipping.
I was trying to solve similar problem with diacritics in Arabic and found workaround:
I have a UITableViewCell with UILabel with arabic text, it's diacritics were cut sometimes
I overrided - (void)drawRect:(CGRect)frame to directly draw NSAttributedString on UITableViewCell
Also I decreased alpha self.arabicLabel.alpha = 0.1; to draw manually on top of label position, I still keep it to calculate cell's height
- (void)drawRect:(CGRect)frame {
[super drawRect:frame];
if (self.viewModel == nil) return;
NSAttributedString *string = [self.viewModel arabicStringWithTajweed];
CGRect originalRect = [self convertRect:self.arabicLabel.frame fromView:self.arabicLabel];
[string drawInRect:originalRect];
}
The core problem on iOS is font substitution. You are specifying a latin font, the font does not contain glyphs for the characters that will be rendered, the system uses a different font to draw the glyphs, but it is still measuring based on the original font.
Option 1, the most robust option, is to manually choose fonts that include glyphs for the characters you will render. When the font assigned to UILabel, or the attributed string it is rendering, contains all the glyphs that will be rendered, and that font has good metrics as most system fonts do, then nothing will be clipped.
Option 2, manually measure the string using glyph bounds. Make a subclass of UILabel and override textRectForBounds and possibly drawText. Measure the string with .usesDeviceMetrics. This is slower that measuring by font metrics and produces different results. For example, the strings "a" and "A" will measure differently.
Option 3, use baseline offset and line height multiple to make room for the diacritics that are being clipped. Choose or compute constant values for each font for each language, and apply those to the attributed string of the UILabel. This can compensate for the different in font metrics between the font you chose and the font that is actually rendering glyphs. We had localized strings with the worst case clipped characters for each language, and used those to compute the offset and height. Different fonts have different worst case clipping characters.
The text below the app icon isn't fully showing, you can see 9 characters out of the 12, is there any way to alter this making it all fit or maybe have two lines instead of one?
No
You can neither change the font size, the letter spacing or number of lines. They all are pretty much fixed.
It is your annoying job to find a app name that fits on all devices in the little space that is available.
Note that the font is not mono-spaced, meaning that all character take up different amount of space - you are going to be able to fit a lot more Is on the screen than Ws or Ms
By design (!) I have two lines title text which I cannot break:
JustWord
VeryLongWordWithoutSpaces
On big iPhones (6 and 6 Plus) text works great,but when I test on smaller iPhone (5 or less) it looks like:
JustWord
VeryLongWordWi...
I cannot find a way how to shrink the text in UILabel.Cutting and truncating are not possible in my situation.
Requirements:Auto Layout, no Storyboards, Swift 2.0, iOS 8.0+
Please try adjustsFontSizeToFitWidth: property for UIlabel. I have tried this and font is setting in one line and font size is automatically adjust.
The two principal ways to do this are to set a minimum font size or a minimum font scale.
I would like to align the start of the text of two UILabels. I aligned the two UILabels (with the yellow and grey background) and used sizeToFit: to shrink the UILabels to the content but the text is not perfectly left aligned. There is a gap on the left. The gap is bigger or smaller depending on the first character. I would like to align the red lines in the following picture. There is even a small gap with the small font in the grey UILabel but it's barely visible.
With the Z character the gap is smaller but still visible by the yellow area left to the Z
A simple UILabel alignment does not help for my specific problem, because the text content is dynamic and not static. So there could be any combination depending on the data I get from the backend. Therefore I was hoping for a UIFont or UILabel attribute that could return the size of the gap based on the current rendering of the text.
I know that there are great UIFont related attributes like baseline, capHeight and ascender one can access to align text but there seems to be no attribute that would return the value of this gap on the left.
If this doesn’t need to be two UILabels, you could have one with attributed text. Then both lines would be subject to the same layout.
I haven't tried the GUI which people have been posting Screen Shots of, however... the behaviour is consistent with the very nature of typography.
The font sizes are completely different, therefore the width of an em is different, subsequently the letter-spacing is also different.
See this: http://www.w3.org/WAI/GL/css2em.htm for more information.
I know this answer doesn't give you what you have asked for, however it should explain why this is happening.
For customisation such as this (if HTML and CSS) I would use a negative / positive margin on the sub text, however seeing as you are using backgrounds use padding (or the equivalent in your GUI).
Use attributed text of UILabel
set indentation as per your requirement
ref screen shot
Select both the labels and add a constraint called align leading edges.
I have a multiline UILabel that I'd like to look similar on different devices (iPhone only). If it was a single line, I'd simply place the auto layout constraints and enable Autoshrink and set the Minimum Font Scale.
The height of the label is dynamic is calculated based on text. To do that, I need the font, which should also be dynamic. The hack I could do is place an invisible label, and set the text I want to fit in a single line into it and calculate the font, but it seems too hacky.
Another thing I'm not that familiar with are size classes. But from what I've read, the same class is shared between all portraits, meaning 3.5, 4, 4.7 and 5.5 inch devices would be bound to the same class, therefore I couldn't use the separate font value?
How would I implement the 'font scaling' for multiline labels so I'm getting similar look on different screen sizes?
If by similar look you mean that the same words appear on each line, I don't think it's possible.
If the label height is dynamic based on the amount of text, the label will simply expand to the height required to show the text with the specified font size.
Font scaling only kicks in when there is not enough space to display the text with the specified font size. Therefore, you must constrain the height or number of lines.
I was able to approximate this using a multiline UILabel with font scale of .5. I set an Equal Heights constraint between it and its superview, with a multiplier of .25. See screenshots below.
In this approach, you would have to dynamically change the Equal Heights multiplier based on the amount of text you have.