Detect whether a font is bold/italic on iOS? - ios

Given a UIFont or a CTFont, how can I tell whether the font is bold/italic?

iOS7 Font Descriptor
No reason to use Core Text, you can simply ask UIFont for the fontDescriptor.
UIFont *font = [UIFont boldSystemFontOfSize:17.0f];
UIFontDescriptor *fontDescriptor = font.fontDescriptor;
UIFontDescriptorSymbolicTraits fontDescriptorSymbolicTraits = fontDescriptor.symbolicTraits;
BOOL isBold = (fontDescriptorSymbolicTraits & UIFontDescriptorTraitBold) != 0;
Going forward this is probably the easiest way to ask about the traits of a font.

If you want to do this with Swift:
extension UIFont {
var isBold: Bool {
return fontDescriptor.symbolicTraits.contains(.traitBold)
}
var isItalic: Bool {
return fontDescriptor.symbolicTraits.contains(.traitItalic)
}
}
Usage:
let font: UIFont = UIFont.preferredFont(forTextStyle: .headline)
if font.isBold {
print("it's bold..")
}

Looking at the font's name won't always work. Consider the font "Courier Oblique" (which is italic) or "HoeflerText-Black" (which is bold), Neither of those contain "bold" or "italic" in their names.
Given a font as a CTFontRef, the proper way to determine whether it's bold or italic is to use the CTFontGetSymbolicTraits function:
CTFontRef font = CTFontCreateWithName((CFStringRef)#"Courier Oblique", 10, NULL);
CTFontSymbolicTraits traits = CTFontGetSymbolicTraits(font);
BOOL isItalic = ((traits & kCTFontItalicTrait) == kCTFontItalicTrait);
BOOL isBold = ((traits & kCTFontBoldTrait) == kCTFontBoldTrait);
NSLog(#"Italic: %i Bold: %i", isItalic, isBold);
CFRelease(font);

Answer for Swift 3/4 based on Arjan's answer:
extension UIFont {
var isBold: Bool {
return fontDescriptor.symbolicTraits.contains(.traitBold)
}
var isItalic: Bool {
return fontDescriptor.symbolicTraits.contains(.traitItalic)
}
}

Related

How do I get a monospace font that respects acessibility settings

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.

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:

Custom font with size classes in iOS Not working correctly

I'm trying to add 2 different font sizes for iphone and ipad layouts using size classes. It works cool with a default System font but doesn't work with custom font. If I add the second size for wR hR then font looks correctly in interface builder(I even checked xml) but in simulator and on device it becomes System instead of my custom font. But if I remove wR hR(or whatever layout I'm using for another size) then font shows correctly. Any idea how to solve this issue? Thanks!
i'm using xCode7
Steps to set custom font .
1) Set fonts as System for size classes
2) Subclass UILabel and override "layoutSubviews" method like:
- (void)layoutSubviews
{
[super layoutSubviews];
// Implement font logic depending on screen size
self.font = [UIFont fontWithName:#"CustomFont" size:self.font.pointSize];
}
May be it will help you.
//Create Custom class of UILabel
class LabelDeviceClass : UILabel{
#IBInspectable var fontBold: Bool = true // add bold bool variable in attributes inspector
#IBInspectable var fontItalic: Bool = true / add italic bool variable in attributes inspector
//this code add font size in attributes inspector so you can give font size
#IBInspectable var iPhoneSize:CGFloat = 0 {
didSet {
if isPhone() {
overrideFontSize(fontSize: iPhoneSize)
}
}
}
#IBInspectable var iPadSize:CGFloat = 0 {
didSet {
if isPad() {
overrideFontSize(fontSize: iPadSize)
}
}
}
func overrideFontSize(fontSize:CGFloat){
var currentFontName = "SofiaPro"//font name you want to use
if fontBold{
currentFontName = "SofiaPro-Bold"
}
else if fontItalic{
currentFontName = "SofiaPro-Italic"
}
if let calculatedFont = UIFont(name: currentFontName, size: fontSize) {
self.font = calculatedFont
}
}
}

System font for both iOS 8 and iOS 9

I want to support both iOS 8 and iOS 9 systems for my app. And maybe iOS 7. As we know, system font for iOS 7 and 8 is Helvetica Neue. But in iOS 9 system font is San-Francisco. And if you don't set Helvetica font explicitly via [UIFont fontWithName:#"HelveticaNeue" size:15];, but use [UIFont systemFontOfSize:15];, you'll get Helvetica for iOS 7 and 8 and San-Francisco for iOS 9 automatically. And it's great!
For interface builder's labels and buttons you can set thin, ultra thin, medium etc. system fonts. It is great too. But how can I set these thin, ultra, medium system fonts in code, programmatically?
Do I need to create a category with a fork for iOS 9 and previous iOS?
I've created this extension:
import Foundation
import UIKit
enum SystemFontWeight : String {
case UltraLight = "HelveticaNeue-UltraLight"
case Thin = "HelveticaNeue-Thin"
case Light = "HelveticaNeue-Light"
case Regular = "HelveticaNeue"
case Medium = "HelveticaNeue-Medium"
case Semibold = "Helvetica-Bold"
case Bold = "HelveticaNeue-Bold"
case Heavy = "HelveticaNeue-CondensedBold"
case Black = "HelveticaNeue-CondensedBlack"
var weightValue:CGFloat? {
if #available(iOS 8.2, *) {
switch self {
case .UltraLight:
return UIFontWeightUltraLight
case .Thin:
return UIFontWeightThin
case .Light:
return UIFontWeightLight
case .Regular:
return UIFontWeightRegular
case .Medium:
return UIFontWeightMedium
case .Semibold:
return UIFontWeightSemibold
case .Bold:
return UIFontWeightBold
case .Heavy:
return UIFontWeightHeavy
case .Black:
return UIFontWeightBlack
}
} else {
return nil
}
}
}
extension UIFont {
static func systemFontOfSize(fontSize:CGFloat, weight:SystemFontWeight) -> UIFont {
if #available(iOS 8.2, *) {
return UIFont.systemFontOfSize(fontSize, weight: weight.weightValue!)
} else {
// Fallback on earlier versions
return UIFont(name: weight.rawValue, size: fontSize)!
}
}
}
Which makes it possible to apply font like this:
myLabel.font = UIFont.systemFontOfSize(14, weight: .Medium)
This will automatically set the correct font for both iOS 8 and iOS 9.
Use + systemFontOfSize:weight:. It's available for iOS 8 and above.
For iOS 7, interface builder settings will work, and for code you will need to create a UIFontDescriptor with the appropriate weight.
Thanks #Leo Natan. But I want to show a code snippet for copy-paste lovers.
UIFont* systemFont = [UIFont respondsToSelector:#selector(systemFontOfSize:weight:)] ? [UIFont systemFontOfSize:25 weight:UIFontWeightThin] : [UIFont fontWithName:#"HelveticaNeue-Thin" size:25];
Thanks #Antoine for posting great answer for swift. Following is Objective C Similar kind of answer if anybody wants. Implement category for UIFont
UIFont+Cat.m
#import "UIFont+Cat.h"
#implementation UIFont (Cat)
+ (UIFont *)systemFontWithSize:(CGFloat)fontSize weight:(CGFloat)weight {
if ([UIFont respondsToSelector:#selector(systemFontOfSize:weight:)]) {
return [UIFont systemFontOfSize:fontSize weight:weight];
}
NSString *fontName = #"HelveticaNeue";
if (weight == UIFontWeightUltraLight) {
fontName = #"HelveticaNeue-UltraLight";
}
else if (weight == UIFontWeightThin) {
fontName = #"HelveticaNeue-Thin";
}
else if (weight == UIFontWeightLight) {
fontName = #"HelveticaNeue-Light";
}
else if (weight == UIFontWeightRegular) {
fontName = #"HelveticaNeue";
}
else if (weight == UIFontWeightMedium) {
fontName = #"HelveticaNeue-Medium";
}
else if (weight == UIFontWeightSemibold) {
fontName = #"Helvetica-Bold";
}
else if (weight == UIFontWeightBold) {
fontName = #"HelveticaNeue-Bold";
}
else if (weight == UIFontWeightHeavy) {
fontName = #"HelveticaNeue-CondensedBold";
}
else if (weight == UIFontWeightBlack) {
fontName = #"HelveticaNeue-CondensedBlack";
}
return [UIFont fontWithName:fontName size:fontSize];
}
#end

How to check if UILabel has attributedText or normal text programmatically?

Is there any way to tell if UILabel has its text set using label.attributedText or label.text property?
The problem is when you set attributedText, text is also updated and vice versa, so it is not possible to check these properties for nil.
Inspired by #lukas-o I have written an extension on UILabel that determines if it contains an attributedText or not. In fact if the NSAttributedString does not contain any attributes, this computed property will evaluate it to not be attributed.
extension UILabel {
var isAttributed: Bool {
guard let attributedText = attributedText else { return false }
let range = NSMakeRange(0, attributedText.length)
var allAttributes = [Dictionary<String, Any>]()
attributedText.enumerateAttributes(in: range, options: []) { attributes, _, _ in
allAttributes.append(attributes)
}
return allAttributes.count > 1
}
}
From the apple docs:
This property is nil by default. Assigning a new value to this property also replaces the value of the text property with the same string data, albeit without any formatting information. In addition, assigning a new a value updates the values in the font, textColor, and other style-related properties so that they reflect the style information starting at location 0 in the attributed string.
You are right, it's not possible to find that out checking one or the other for nil. One way you could know that the text is attributed would be to use something like:
NSMutableArray *strAttrs = [NSMutableArray new];
NSMutableArray *strRanges = [NSMutableArray new];
[label.attributedText enumerateAttributesInRange:NSMakeRange(0, label.attributedText.length) options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
[strAttrs addObject:attrs];
[strRanges addObject:[NSValue valueWithRange:(range)]];
}];
This way you could get see whether more than one attribute is there. You could also compare the attributes whether they match your standard attributes and assume that the text property has been set only in this case.
This is what I use. If range length equals the unattributed text length then the text only has a single attribute and is therefore unattributed.
NSRange range;
[label.attributedText attributesAtIndex:0 effectiveRange:&range];
BOOL isAttributed = label.text.length==range.length;
Here is the implementation I came up with, with some slightly altered logic. It is a Swift port of #Vallette 's accepted answer, with additional guard statements.
The function will only return true if the attributedText is not nil, not empty, and has at least one attribute that does not apply across the entire range of the text:
extension UILabel {
var isPartiallyAttributed: Bool {
guard let attributedText = attributedText else {
return false
}
guard !attributedText.string.isEmpty else {
return false
}
var range = NSRange()
attributedText.attributes(at: 0, effectiveRange: &range)
return attributedText.string.count != range.length
}
}
The better way would be to check if Range length of attributes are the same as length of the string. If it's the same you can assume this is non-attributed label.
However, i don't know 100% reliable way to check for attributed text if NSAttributes are applied to the whole UILabel text.
private extension UILabel {
var hasAttributedText: Bool {
guard let attributedText = attributedText,
attributedText.string.isEmpty == false else { return false }
var range = NSRange(location: 0, length: attributedText.length)
_ = attributedText.attributes(at: 0,
effectiveRange: &range)
return range.length != attributedText.length
}
}

Resources