-
-
Save zwaldowski/15e7ea7cfd792ceda808 to your computer and use it in GitHub Desktop.
Dynamic Type, made dynamic - with Avenir!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DynamicTypeLabel.swift | |
// | |
// Created by Indragie on 10/16/14. | |
// Copyright (c) 2014 Indragie Karunaratne. All rights reserved. | |
// | |
import UIKit | |
class DynamicTypeLabel : UILabel { | |
private var textStyle: TextStyle? = nil | |
dynamic func commonInit() { | |
textStyle = TextStyle(font: font) | |
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("noteDynamicTypeSettingChanged"), name: UIContentSizeCategoryDidChangeNotification, object: nil) | |
} | |
required init(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
commonInit() | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
commonInit() | |
} | |
deinit { | |
NSNotificationCenter.defaultCenter().removeObserver(self) | |
} | |
override func didMoveToWindow() { | |
super.didMoveToWindow() | |
if let window = window { | |
noteDynamicTypeSettingChanged() | |
} | |
} | |
@objc func noteDynamicTypeSettingChanged() { // UIContentSizeCategoryDidChangeNotification | |
if let textStyle = textStyle { | |
font = UIFont(appTextStyle: textStyle) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIFont+AppDefines.swift | |
// | |
// Created by Zachary Waldowski on 11/27/14. | |
// Copyright (c) 2014 Big Nerd Ranch. All rights reserved. | |
// | |
import UIKit | |
public enum TextStyle { | |
case Headline | |
case Body | |
case Subheadline | |
case Footnote | |
case Caption1 | |
case Caption2 | |
init?(font: UIFont) { | |
let styles = [ UIFontTextStyleHeadline, UIFontTextStyleBody, UIFontTextStyleSubheadline, UIFontTextStyleFootnote, UIFontTextStyleCaption1, UIFontTextStyleCaption2 ] | |
var nativeStyle: String? | |
for style in styles { | |
if font == UIFont.preferredFontForTextStyle(style) { | |
nativeStyle = style | |
break | |
} | |
} | |
if let style = nativeStyle { | |
self.init(string: style) | |
} else { | |
return nil | |
} | |
} | |
private init!(string: String) { | |
switch string { | |
case UIFontTextStyleHeadline: self = .Headline | |
case UIFontTextStyleBody: self = .Body | |
case UIFontTextStyleSubheadline: self = .Subheadline | |
case UIFontTextStyleFootnote: self = .Footnote | |
case UIFontTextStyleCaption1: self = .Caption1 | |
case UIFontTextStyleCaption2: self = .Caption2 | |
default: return nil | |
} | |
} | |
private var stringValue: String { | |
switch self { | |
case .Headline: return UIFontTextStyleHeadline | |
case .Body: return UIFontTextStyleBody | |
case .Subheadline: return UIFontTextStyleSubheadline | |
case .Footnote: return UIFontTextStyleFootnote | |
case .Caption1: return UIFontTextStyleCaption1 | |
case .Caption2: return UIFontTextStyleCaption2 | |
} | |
} | |
private var letterTransformValue: UIFontDescriptor.LetterTransform { | |
switch self { | |
case .Subheadline, .Caption1: return .AllCaps | |
default: return .Regular | |
} | |
} | |
} | |
public extension UIFont { | |
convenience init(appTextStyle textStyle: TextStyle, size: CGFloat = 0) { | |
let descriptor = UIFontDescriptor(preferredTextStyle: textStyle.stringValue, familyOverride: "Avenir Next", transform: textStyle.letterTransformValue) | |
self.init(descriptor: descriptor, size: size) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// UIFontDescriptor+DynamicType.swift | |
// | |
// Created by Zachary Waldowski on 11/27/14. | |
// Copyright (c) 2014 Big Nerd Ranch. All rights reserved. | |
// | |
import UIKit | |
extension UIFontDescriptor { | |
enum LetterTransform { | |
case Regular | |
case AllCaps | |
case SmallCaps | |
case PetiteCaps | |
private var upperCaseFeature: [NSObject: AnyObject] { | |
var selector: Int | |
switch self { | |
case .AllCaps: selector = kUpperCaseSmallCapsSelector | |
default: selector = kDefaultUpperCaseSelector | |
} | |
return [ | |
UIFontFeatureTypeIdentifierKey: kUpperCaseType, | |
UIFontFeatureSelectorIdentifierKey: selector | |
] | |
} | |
private var lowerCaseFeature: [NSObject: AnyObject] { | |
var selector: Int | |
switch self { | |
case .Regular: selector = kDefaultLowerCaseSelector | |
case .AllCaps, .SmallCaps: selector = kUpperCaseSmallCapsSelector | |
case .PetiteCaps: selector = kUpperCasePetiteCapsSelector | |
} | |
return [ | |
UIFontFeatureTypeIdentifierKey: kLowerCaseType, | |
UIFontFeatureSelectorIdentifierKey: selector | |
] | |
} | |
} | |
convenience init(preferredTextStyle: String, familyOverride family: String, transform: LetterTransform) { | |
let origDescriptor = UIFontDescriptor.preferredFontDescriptorWithTextStyle(preferredTextStyle) | |
var attributes = origDescriptor.fontAttributes() | |
attributes.removeValueForKey(UIFontDescriptorTextStyleAttribute) | |
// simple "as" conversion won't work, CoreText isn't audited | |
let origBridged: CTFontDescriptor = unsafeDowncast(origDescriptor) | |
let newDesc: CTFontDescriptor? = CTFontDescriptorCreateCopyWithFamily(origBridged, family) | |
let newAttributes = newDesc.map(CTFontDescriptorCopyAttributes) ?? origDescriptor.fontDescriptorWithFamily(family).fontAttributes() | |
for (key, value) in newAttributes { | |
attributes.updateValue(value, forKey: key) | |
} | |
var features = [ transform.upperCaseFeature, transform.lowerCaseFeature ] | |
if let existingFeatures = attributes[UIFontDescriptorFeatureSettingsAttribute] as? [[NSObject: AnyObject]] { | |
features += existingFeatures.filter { dict in | |
if let type = dict[UIFontFeatureTypeIdentifierKey] as? Int { | |
return type != kUpperCaseType && type != kLowerCaseType | |
} | |
return true | |
} | |
} | |
attributes[UIFontDescriptorFeatureSettingsAttribute] = features | |
self.init(fontAttributes: attributes) | |
} | |
} |
Nice work Zach. I need to play with the UIFontDescriptors to get a better idea of what they are capable of ... but I like what you've done.
Thanks, @hitsvilleusa!
Important side note - these techniques don't use UIApplication.preferredContentSizeCategory
, so they're suitable for achieving Dynamic Type support in an extension or a framework marked for extension-safety (where UIApplication
cannot be referenced).
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I can't believe that first class isn't the default behavior. It seems like we are conscious when we opt-in to dynamic type, that to have to do all of the updates "outside" of the label just seem silly. Very nice solution.