Skip to content

Instantly share code, notes, and snippets.

@zwaldowski
Forked from indragiek/DynamicTypeLabel.swift
Last active April 26, 2016 20:18
Show Gist options
  • Save zwaldowski/15e7ea7cfd792ceda808 to your computer and use it in GitHub Desktop.
Save zwaldowski/15e7ea7cfd792ceda808 to your computer and use it in GitHub Desktop.
Dynamic Type, made dynamic - with Avenir!
//
// 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)
}
}
}
//
// 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)
}
}
//
// 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)
}
}
@hitsvilleusa
Copy link

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.

@hitsvilleusa
Copy link

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.

@zwaldowski
Copy link
Author

Thanks, @hitsvilleusa!

@zwaldowski
Copy link
Author

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