I have a case where I want to display some sort of Footnote-Text in a UILabel. The text is prefixed with a number as the corresponding index.
My first idea to solve this problem was to simply create a view-setup with an UILabel for the mark and the text each. This, however, involved a lot of fiddling with constraints and auto-layout, since I wanted to list all footnotes in a table.
Thus I came up with the idea of using NSMutableAttributedString and set the baseline/font-size for the mark and the text respectively. The resulting NSAttributedString could then be used in any Layout with an UILabel.
let footnote: NSMutableAttributedString = .init(string: mark, attributes: [
.foregroundColor: UIColor.myColor,
.font: UIFont.myFont(7),
.baselineOffset: 10,
])
disclaimer.append(.init(string: text, attributes: [
.foregroundColor: UIColor.myColor,
.font: UIFont.myFont(14),
]))
Or so I thought. Seems like the layout of either the UILabel or the UITableViewRow do not like text with alternating baselines, as the UILabel never resized to more 2 Lines. Everything beyond those 2 lines was cut off. Even setting the preferredMayLayoutWidth to help the layout out a bit did not work. It did scale the Label to the correct height, but the text was still missing as if it was cut-off.
So, to answer my own question (and to share this knowledge with whoever might stumble across the same problem), I was able to solve this problem with a very simple (admittedly not very elegant) fix.
All I had to do was to add some text before my mark with the modified baselineOffset. This way, the layout seems to be able to calculate the correct height while still rendering the mark and text properly. Since I did not want any additional text in my cells, I've used a Zero-Width Space character.
let footnote: NSMutableAttributedString = .init(string: "\u{200B}", attributes: [:])
footnote.append(.init(string: mark, attributes: [
.foregroundColor: UIColor.myColor,
.font: UIFont.myFont(7),
.baselineOffset: 10,
]))
footnote.append(.init(string: text, attributes: [
.foregroundColor: UIColor.myColor,
.font: UIFont.myFont(14),
]))
I am trying to make an iOS app that can make possible to write and edit musical sheet.
I was wondering how can I visualise it.
I did not found any complete framework in swift that can make it possible.
But I found this font bravura, but i did not found any example of use.
This is an OpenType Font.
Reading the documentation I should be able to decide through unicode characters, how to change the position of the next element.
But no it doesn't seem to work. At first I thought it was because of the ligatures as not all editors support it, I'm not a font expert and I could be wrong.
So I used the NSAttributedString class to modify these parameters. But the result is still not what is expected.
This is my code :
textView.font = UIFont(name: "Bravura-Text", size: 40)
let text = "\u{E014}\u{EB99}\u{E0A4} \u{E014}\u{EB91}\u{E0A4}"
let attributes = [
NSAttributedString.Key.font : UIFont(name: "Bravura-Text", size: 40)!,
NSAttributedString.Key.ligature : 2
] as [NSAttributedString.Key : Any]
let attributedString = NSAttributedString(string: text, attributes: attributes)
textView.attributedText = attributedString
E014 = Displaying a 5 line staff
EB99 = Lower by two staff positions
EB91 = Raise by two staff positions
E0A4 = The black notehead
This is the result :
result
Any suggestions to make it possible to use this font and its features on iOS?
I would like to write a musical sheet like this one.
Layout of music notation require higher-level layout rather than just displaying a line of text. (Analogous to Web pages using HTML as high-level layout rather than just lines of text.) Fonts can provide the basic symbols, and all of the basic symbols are encoded in Unicode. But you'll need some library that can read a music-notation markup language and layout the content.
You can probably find the kind of resources you'll need at https://www.w3.org/community/music-notation/
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.
As I understand it, a string contains glyphs and a glyph might consist of individual characters. For me this is a problem, as I would like to change the color of some diacritics in a string.
Let's say we have the following string:
วาีม
For this string I would like to make the consonants a different color as it's diacritic. I.e.: I want a different color for วาม and ี.
From my tests it seems that I am only able to color individual glyphs. It seems I can not change the color at a character (diacritic) level. Some example code:
let text = NSMutableAttributedString(string: "วาีม")
text.addAttribute(NSForegroundColorAttributeName, value: UIColor.blue, range: NSMakeRange(0, 1))
text.addAttribute(NSForegroundColorAttributeName, value: UIColor.red, range: NSMakeRange(1, 1))
text.addAttribute(NSForegroundColorAttributeName, value: UIColor.green, range: NSMakeRange(2, 1))
text.addAttribute(NSForegroundColorAttributeName, value: UIColor.orange, range: NSMakeRange(3, 1))
label.attributedText = text
The above would render as follows:
As can be seen the diacritic is not rendered with a green color.
Does anyone know if there is some way to achieve the result I want?
I doubt you'll find a good way to do this. The glyph for าี in the font is a completely different thing than the glyph for า. For example, in the Thonburi font on my Mac, the former is glyph 1507 and the latter is glyph 78. Each glyph entry in the font is a completely separate little description of how to draw the glyph. For the combined glyph, the diacritic is not a separate thing. The system has no way to know when it's drawing the base character and when it's drawing the diacritic. It's just drawing one thing. So, it can't apply different colors.
I'm completely unfamiliar with Thai, so I'm just speculating for this next part, although it's certainly true for some languages: I suspect there are glyphs for combining sequences which are radically different from what you'd get by just overlaying the component parts on top of each other. So, even in principle, it's not clear that there's a visually separable diacritic shape vs. the base character.
An alternative is to put two labels on top of one another; one containing the text with the diacritics in the colour you want for the diacritics; one on top of it with the same text but without the diacritics, in the colour you want for the text.
When rendering, the identical bits of the text should cancel out exactly, leaving you with text and diacritics in the right colours.
I have a UITextView set with some text with both chinese and latin characters.
As you can see, the lineSpace is different depending if there is a latin character or not in the line above. Obviously, I need to remove this difference and to equalize the lineSpace.
Here are the statements :
I work with an AttributedString
The font is different depending if the character is latin or chinese
The point above needs to stay true
The lineHeight is different between the 2 fonts
I almost sure than the reason of my problem comes from this last point but since the lineHeight of the font is not editable, I'm a bit stuck.
I already try to play with those properties
style.minimumLineHeight = mylineSpacing;
style.maximumLineHeight = mylineSpacing;
style.lineHeightMultiple = mylineSpacing;
style.lineSpacing = 0;
NSDictionary *attributes = #{
NSParagraphStyleAttributeName : style
};
It doesn't work. I'm thinking about using CoreText or even CoreGraphic to redraw all the characters one by one but maybe there is an easier solution. And if not, which Kit should I use ?
PS : I'm not chinese and I used random chinese text for this screenshot and I apologize to an eventual chinese stackoverflow user if my text looks stupid / no sense / or offending in any way.