How do I get a monospace font that respects acessibility settings - ios

let bodyFontDescriptor = UIFontDescriptor
.preferredFontDescriptor(withTextStyle: UIFontTextStyle.body)
let bodyMonospacedFontDescriptor = bodyFontDescriptor.addingAttributes(
[
UIFontDescriptorFeatureSettingsAttribute: [
[
UIFontFeatureTypeIdentifierKey: kTextSpacingType,
UIFontFeatureSelectorIdentifierKey: kMonospacedTextSelector
]
]
])
let bodyMonospacedFont = UIFont(descriptor: bodyMonospacedFontDescriptor, size: 0.0)
textview.font = bodyMonospacedFont
This produces text with characters of variable width.
I need to get a monospace font without hardcoding courier new
and fixed size.
Deployment target is ios 9.0

Here is an extension to UIFontDescriptor that returns a preferred monospaced font descriptor for a given text style. There is no simple way to get a fully monospaced font using UIFont or UIFontDescriptor. This solution attempts to find a good monospaced font and falls back to Courier if needed.
extension UIFontDescriptor {
static let monoDescriptor: UIFontDescriptor = {
// Attempt to find a good monospaced, non-bold, non-italic font
for family in UIFont.familyNames {
for name in UIFont.fontNames(forFamilyName: family) {
let f = UIFont(name: name, size: 12)!
let fd = f.fontDescriptor
let st = fd.symbolicTraits
if st.contains(.traitMonoSpace) && !st.contains(.traitBold) && !st.contains(.traitItalic) && !st.contains(.traitExpanded) && !st.contains(.traitCondensed) {
return fd
}
}
}
return UIFontDescriptor(name: "Courier", size: 0) // fallback
}()
class func preferredMonoFontDescriptor(withTextStyle style: UIFontTextStyle) -> UIFontDescriptor {
// Use the following line if you need a fully monospaced font
let monoDescriptor = UIFontDescriptor.monoDescriptor
// Use the following two lines if you only need monospaced digits in the font
//let monoDigitFont = UIFont.monospacedDigitSystemFont(ofSize: 0, weight: .regular)
//let monoDescriptor = monoDigitFont.fontDescriptor
// Get the non-monospaced preferred font
let defaultFontDescriptor = preferredFontDescriptor(withTextStyle: style)
// Remove any attributes that specify a font family or name and remove the usage
// This will leave other attributes such as size and weight, etc.
var fontAttrs = defaultFontDescriptor.fontAttributes
fontAttrs.removeValue(forKey: .family)
fontAttrs.removeValue(forKey: .name)
fontAttrs.removeValue(forKey: .init(rawValue: "NSCTFontUIUsageAttribute"))
let monospacedFontDescriptor = monoDescriptor.addingAttributes(fontAttrs)
return monospacedFontDescriptor.withSymbolicTraits(defaultFontDescriptor.symbolicTraits) ?? monospacedFontDescriptor
}
}
Note the comments about whether you need a font that is fully monospaced or a font that just has monospaced digits. Comment/Uncomment those lines to suit your specific needs.
Sample usage:
let bodyMonospacedFont = UIFont(descriptor: .preferredMonoFontDescriptor(withTextStyle: .body), size: 0)
textview.font = bodyMonospacedFont
The following is some test code to confirm that the results of preferredMonoFontDescriptor(withTextStyle:) works properly for all styles:
let textStyles: [UIFontTextStyle] = [ .body, .callout, .caption1, .caption2, .footnote, .headline, .subheadline, .largeTitle, .title1, .title2, .title3 ]
for style in textStyles {
let nfont = UIFont(descriptor: .preferredFontDescriptor(withTextStyle: style), size: 0)
let mfont = UIFont(descriptor: .preferredMonoFontDescriptor(withTextStyle: style), size: 0)
print(style)
print(nfont)
print(mfont)
}
If you compare each pair of results, they have the same size, weight, and style, just a different font.

Related

iOS using custom Arabic/Cyrillic fonts automatically

I'm trying to learn if it is possible to use a custom Arabic and Cyrillic fonts without having to do a switch/if-else on the user's language setting.
I can successfully use my custom font in the app. I'd like to supply a custom Ar/Cy font the same way, and I know I could build it into the app. If I have my font SpecialFont.otf and also supply SpecialFont-CY.otf how would the OS know to use SpecialFontCY.otf when the user is using a Cyrillic language? Ideally the OS will know the user's primary font and would be able to select a font that matches/includes the correct glyphs for the language.
PS. this is not a question on how to use a custom font, I can do that. I want to know how to supply multiple fonts for various languages to fully support the world without writing code like this:
if NSLocale.preferredLanguages.first == "Arabic"
let myFont = UIFont(name:"SpecialFont-AR", size: 17)
else if NSLocale.preferredLanguages.first == "Russian"
let myFont = UIFont(name:"SpecialFont-CY", size: 17)
...etc
Rather than using a UIFont, you want a UIFontDescriptor. With that you can set the font attribute cascadeList, which tells the system what order to select fonts based on glyph availability (i.e. look in SpecialFont, but if you can't find a glyph for ب, try SpecialFont-CY, and then SpecialFont-AR).
The point of a cascade list is to select the correct font for a given glyph. This way, if a string contains Cyrillic, Arabic, and Latin mixed together, it'll still work fine.
For example:
// Start with your base font
let font = UIFont(name:"SpecialFont", size: 17)!
// Create the ordered cascade list.
let cascadeList = [
UIFontDescriptor(fontAttributes: [.name: "SpecialFont-AR"]),
UIFontDescriptor(fontAttributes: [.name: "SpecialFont-CY"]),
]
// Create a new font descriptor based on your existing font, but adding the cascade list
let cascadedFontDescriptor = font.fontDescriptor.addingAttributes([.cascadeList: cascadeList])
// Make a new font base on this descriptor
let cascadedFont = UIFont(descriptor: cascadedFontDescriptor, size: font.pointSize)
This is covered in detail in Creating Apps for a Global Audience (WWDC 2018).
No you can't, but you can define a simple extension to DRY your code:
extension UIFont {
static func preferred(ofSize size: CGFloat) -> UIFont{
switch NSLocale.preferredLanguages.first {
case "Arabic": return UIFont(name:"SpecialFont-AR", size: size)!
case "Russian": return UIFont(name:"SpecialFont-CY", size: size)!
default: return UIFont.systemFont(ofSize: size) // etc.
}
}
}
Now all you have to do is:
let myFont = UIFont.preferred(ofSize: 17)
You will need to check this somehow in order to determine what is the right language to set.
If you don't want to use if/else syntax, you can use Ternary Conditional Operator.
let myFont = (NSLocale.preferredLanguages.first == "Arabic") ? UIFont(name:"SpecialFont-AR", size: 17) : UIFont(name:"SpecialFont-CY", size: 17)
Or more readable, like this:
let fontName = (NSLocale.preferredLanguages.first == "Arabic") ? "SpecialFont-AR" : "SpecialFont-CY"
let myFont = UIFont(name: fontName, size: 17)

How to set custom bold font programmatically - swift

I am trying to set the font in a UILabel to a custom font but also make it bold without success. My effort so far is below:
CUSTOM FONT:
cell.folderName.font = UIFont.init(name: "American Typewriter", size: 20)
How do I make the above font also bold/ or italic?
This enum can be used for AmericanTypewriter type.
public enum americanTypewriter: String {
case typewriter = "AmericanTypewriter"
case bold = "AmericanTypewriter-Bold"
case condensed = "AmericanTypewriter-Condensed"
case condensedBold = "AmericanTypewriter-CondensedBold"
case condensedLight = "AmericanTypewriter-CondensedLight"
case light = "AmericanTypewriter-Light"
public func font(size: CGFloat) -> UIFont {
return UIFont(name: self.rawValue, size: size)!
}
}
For Bold
cell.folderName.font = UIFont.init(name: "AmericanTypewriter-Bold", size: 20)
Font American Typewriter doesn't have italic style. But it has bold style.
So if you need to set bold font, just use
"AmericanTypewriter-Bold"
All American Typewriter font's styles:
AmericanTypewriter-CondensedBold
AmericanTypewriter-Condensed
AmericanTypewriter-CondensedLight
AmericanTypewriter
AmericanTypewriter-Bold
AmericanTypewriter-Semibold
AmericanTypewriter-Light

UIFont monospaced digits + small caps

I'm trying to create a UIFont with the following attributes:
Upper Case Small Caps
Lower Case Small Caps
Monospaced Digits
I'm using the system font (San Francisco), which does supports all theses features.
As far as I know, the only way to do it is to use multiple UIFontDescriptor.
Here is the code I'm using:
extension UIFont {
var withSmallCaps: UIFont {
let upperCaseFeature = [
UIFontDescriptor.FeatureKey.featureIdentifier : kUpperCaseType,
UIFontDescriptor.FeatureKey.typeIdentifier : kUpperCaseSmallCapsSelector
]
let lowerCaseFeature = [
UIFontDescriptor.FeatureKey.featureIdentifier : kLowerCaseType,
UIFontDescriptor.FeatureKey.typeIdentifier : kLowerCaseSmallCapsSelector
]
let features = [upperCaseFeature, lowerCaseFeature]
let smallCapsDescriptor = self.fontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.featureSettings : features])
return UIFont(descriptor: smallCapsDescriptor, size: pointSize)
}
var withMonospacedDigits: UIFont {
let monospacedDigitsFeature = [
UIFontDescriptor.FeatureKey.featureIdentifier : kNumberSpacingType,
UIFontDescriptor.FeatureKey.typeIdentifier : kMonospacedNumbersSelector
]
let monospacedDigitsDescriptor = self.fontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.featureSettings : [monospacedDigitsFeature]])
return UIFont(descriptor: monospacedDigitsDescriptor, size: pointSize)
}
}
I should be able to obtain a font with all the characteristics mentioned earlier with this line of code:
let font = UIFont.systemFont(ofSize: 16, weight: .regular).withSmallCaps.withMonospacedDigits
// OR
let font = UIFont.monospacedDigitSystemFont(ofSize: 16, weight: .regular).withSmallCaps
But for some reasons, it does not work. I can't get the font to have monospaced digits while having small caps at the same time.
What am I doing wrong?
I figured out why it does not work thanks to the reference document linked by #Carpsen90.
It seems like the Number Spacing feature is exclusive.
As stated in the document:
Features are classified as "exclusive" and "nonexclusive." This indicates whether several different selectors within a given feature type may be selected at once. Thus, it is possible to have both common and rare ligatures turned on, whereas it is impossible to display a given fraction as simultaneously vertical and diagonal.
So having both monospaced digits + small caps features at the same time is impossible.
EDIT:
I misread the document. Selectors of that feature are exclusive. But not the whole feature. So it should be possible.
Have a look at the reference document for more details.
I would suggest using attributed strings with small caps for all glyphs but numbers, and another font for monospaced digits. here is some sample code:
let monoSpacedDigits = UIFont.systemFont(ofSize: 13, weight: .medium).withMonospacedDigits
let smallCaps = UIFont.systemFont(ofSize: 16, weight: .regular).withSmallCaps
let attributedString = NSMutableAttributedString(string: """
H3ll0 7here
1111111111
2222222222
3333333333
4444444444
5555555555
6666666666
7777777777
8888888888
9999999999
0000000000
""", attributes: [NSAttributedStringKey.font : smallCaps])
do {
let regex = try NSRegularExpression(pattern: "[0-9]")
let range = NSRange(0..<attributedString.string.utf16.count)
let matches = regex.matches(in: attributedString.string, range: range)
for match in matches.reversed() {
attributedString.addAttribute(NSAttributedStringKey.font, value: monoSpacedDigits, range: match.range)
}
} catch {
// Do error processing here...
print(error)
}
myLabel.attributedText = attributedString
I've used a size of 13 and a medium weight to make the monospaced digits look as similar as possible to the small caps.

iOS - How to detect whether a font is bold/black/heavy...?

I want to detect the style(bold ,heavy, black) of a font. But I can just detect whether the font is bold.
BOOL isBold = (font.fontDescriptor.symbolicTraits & UIFontDescriptorTraitBold)!=0;
There is no black or heavy trait in UIFontDescriptorSymbolicTraits.
A way is to check the font name whether contains 'black' or 'heavy' string, but this seems unreliable.
There is UIFontWeightTrait, but it's just for UIFont systemFontOfSize: weight:
And I want to create my custom font with a style if there is available these style.
To check if it's Heavy or Black:
NSString *fontUsage = font.fontDescriptor.fontAttributes[#"NSCTFontUIUsageAttribute"];
if ([fontUsage isEqualToString:#"CTFontHeavyUsage"]) {
NSLog(#"It's Heavy");
}
else if ([fontUsage isEqualToString:#"CTFontBlackUsage"]) {
NSLog(#"It's Black");
}
The list of other usage options are very simple, just put usage in format "CTFont......Usage", the list I tested are:
//CTFontUltraLightUsage,CTFontThinUsage,CTFontLightUsage,CTFontMediumUsage,CTFontDemiUsage
And How to create a font with usage, like heavy:
UIFontDescriptor *fontDescriptor = [[UIFontDescriptor alloc] initWithFontAttributes:#{#"NSCTFontUIUsageAttribute":#"CTFontHeavyUsage"}];
UIFont *font = [UIFont fontWithDescriptor:fontDescriptor size:17];
Swift3 version for checking:
if let fontUsage = font.fontDescriptor.fontAttributes["NSCTFontUIUsageAttribute"] as? String {
if fontUsage == "CTFontHeavyUsage" {
print("It's Heavy")
}
else if fontUsage == "CTFontBlackUsage" {
print("It's Black")
}
}
Swift Version for detecting Heavy/Black style of font
let fontUsage = font.fontDescriptor.fontAttributes["NSCTFontUIUsageAttribute"] as! String
if fontUsage == "CTFontHeavyUsage"{
print("It is heavy")
}
else if fontUsage == "CTFontBlackUsage"{
print("it's black")
}
and to create font with attributes:
let fontDescriptor = UIFontDescriptor(fontAttributes: ["NSCTFontUIUsageAttribute" : "CTFontHeavyUsage"])
let font = UIFont(descriptor: fontDescriptor, size: 17)
This gives you whether a font is bold or not:
var isBold = label.font.fontDescriptor.symbolicTraits.contains(.traitBold)
Here is some experiement: this gives you the correct answer even if the a bold font is set, or if you set the font's symbolicTraits manually to be bold:

iOS Monospaced Custom Font

I have a custom font included in my Xcode 7, iOS 9 targeted project. I want to make the font monospaced. I tried this, and didn't work:
let originalFont = UIFont(name: "My Custom Font", size: 18)
let originalFontDescriptor = originalFont!.fontDescriptor()
let fontDescriptorFeatureSettings = [
[
UIFontFeatureTypeIdentifierKey: kNumberSpacingType,
UIFontFeatureSelectorIdentifierKey: kMonospacedNumbersSelector
]
]
let fontDescriptorAttributes = [UIFontDescriptorFeatureSettingsAttribute: fontDescriptorFeatureSettings]
let fontDescriptor = originalFontDescriptor.fontDescriptorByAddingAttributes(fontDescriptorAttributes)
let font = UIFont(descriptor: fontDescriptor, size: 0)
topLabel.font = font
With or without above code, the label displayed in proper custom font. It's just above code doesn't do anything.
My following answer is only making numbers (not the whole font) of an existing font monospaced (if the font supports it)
At least I was searching for making numbers monospaced when finding this Thread. So I hope it will help although it answers another question.
This works just fine, tested on Swift 5 and iOS14+13:
(As long as "your font is supporting the monospaced digits feature".)
extension UIFont {
var monospacedDigitFont: UIFont {
let oldFontDescriptor = fontDescriptor
let newFontDescriptor = oldFontDescriptor.monospacedDigitFontDescriptor
return UIFont(descriptor: newFontDescriptor, size: 0)
}
}
private extension UIFontDescriptor {
var monospacedDigitFontDescriptor: UIFontDescriptor {
let fontDescriptorFeatureSettings = [[UIFontDescriptor.FeatureKey.featureIdentifier: kNumberSpacingType, UIFontDescriptor.FeatureKey.typeIdentifier: kMonospacedNumbersSelector]]
let fontDescriptorAttributes = [UIFontDescriptor.AttributeName.featureSettings: fontDescriptorFeatureSettings]
let fontDescriptor = self.addingAttributes(fontDescriptorAttributes)
return fontDescriptor
}
}
Then you can use it on any label like this:
/// Label with monospacing activated
myLabel.font = myLabel.font.monospacedDigitFontDescriptor
/// Label with monospacing not activated (default is proportional spacing)
myLabel.font = myLabel.font
(source: https://blog.usejournal.com/proportional-vs-monospaced-numbers-when-to-use-which-one-in-order-to-avoid-wiggling-labels-e31b1c83e4d0)
The code you are using is not making font monospaced.
It's tweaking font to render digits in monospace mode. So all with this font digits will have same width.
Below is an example with 4 labels, 1 is using custom font Docis Light, 2nd is Docis Light with monospaced digits on, 3rd is system font of same size, 4th is system font with monospaced digits on:
As you see, this custom font already supports monospace digits feature out of the box with no tweak required.
If you need to use monospaced (not just digits) font, you have to use custom monospaced font (designed to be monospaced) or you can use built-in iOS monospaced fonts such as Courier or Menlo (See all available iOS fonts at http://iosfonts.com/)
This is how they look like with same scenario:
With or without tweaking, they are already monospaced and the digits are monospaced as well.
I answered similar question here, probably, I should just link the answer instead of images but it so much more visual.
Don't forget to import the header file. Hope it will work. This solution is in Objective-C
#import <CoreTextArcView.h>
UIFont *const existingFont = [UIFont preferredFontForTextStyle: UIFontTextStyleBody];
UIFontDescriptor *const existingDescriptor = [existingFont fontDescriptor];
NSDictionary *const fontAttributes = #{
UIFontFeatureTypeIdentifierKey
UIFontDescriptorFeatureSettingsAttribute: #[
#{
UIFontFeatureTypeIdentifierKey: #(kNumberSpacingType),
UIFontFeatureSelectorIdentifierKey: #(kMonospacedNumbersSelector)
}]
};
UIFontDescriptor *const monospacedDescriptor = [existingDescriptor fontDescriptorByAddingAttributes: fontAttributes];
UIFont *const proportionalFont = [UIFont fontWithDescriptor: monospacedDescriptor size: [existingFont pointSize]];

Resources