Skip to content

Instantly share code, notes, and snippets.

@juliengdt
Created September 26, 2016 16:04
Show Gist options
  • Save juliengdt/a9a34e4515017138e0ff8228a7d2d27d to your computer and use it in GitHub Desktop.
Save juliengdt/a9a34e4515017138e0ff8228a7d2d27d to your computer and use it in GitHub Desktop.
Dynamic Type handling in Swift 3, just by using Dynamic Type as Font everywhere in the app, no more manual refresh on notification needed
//
// FontConfiguration.swift
// juliengdt
//
// Created by Julien Goudet on 26/09/2016.
// Based on: https://www.iphonelife.com/blog/31369/swift-programming-101-mastering-dynamic-type-ios
//
import Foundation
import UIKit
let textStyles:[UIFontTextStyle] = [.title1,
.headline,
.subheadline,
.body,
.footnote,
.caption2]
// Dynamic Type change handler
// Adopt this on controls to adapt to Dynamic Type changes
protocol DynamicTypeProtocol {
var dynamicTypeObserver: Bool {get set}
}
extension UILabel : DynamicTypeProtocol {
@IBInspectable var dynamicTypeObserver: Bool {
get {
return DynamicTypeManager.shared.isControlRegistered(control: self)
}
set {
DynamicTypeManager.shared.registerControl(control: self, fontKeyPath: "font")
}
}
}
extension UIButton : DynamicTypeProtocol {
@IBInspectable var dynamicTypeObserver: Bool {
get {
return DynamicTypeManager.shared.isControlRegistered(control: self)
}
set {
DynamicTypeManager.shared.registerControl(control: self, fontKeyPath: "titleLabel?.font")
}
}
}
extension UITextField : DynamicTypeProtocol {
@IBInspectable var dynamicTypeObserver: Bool {
get {
return DynamicTypeManager.shared.isControlRegistered(control: self)
}
set {
DynamicTypeManager.shared.registerControl(control: self, fontKeyPath: "font")
}
}
}
extension UISegmentedControl: DynamicTypeProtocol {
@IBInspectable var dynamicTypeObserver: Bool {
get {
return DynamicTypeManager.shared.isControlRegistered(control: self)
}
set {
let currentAttributes = self.titleTextAttributes(for: .normal)
DynamicTypeManager.shared.registerControl(control: self, font: currentAttributes?[NSFontAttributeName] as! UIFont) { (fontStyle) -> () in
let changedAttributes = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: UIFontTextStyle(rawValue: fontStyle))]
self.setTitleTextAttributes(changedAttributes, for: .normal)
}
}
}
}
extension UITextView: DynamicTypeProtocol {
@IBInspectable var dynamicTypeObserver: Bool {
get {
return DynamicTypeManager.shared.isControlRegistered(control: self)
}
set {
DynamicTypeManager.shared.registerControl(control: self, fontKeyPath: "font")
}
}
}
// Contains the font key path and style
class DynamicTypeControlValues: NSObject {
var fontKeyPath: String?
var fontStyle: String
var closure: ((_ fontStyle: String) -> ())?
init(fontKeyPath: String, fontStyle: String) {
self.fontKeyPath = fontKeyPath
self.fontStyle = fontStyle
}
init(style: String, closure:@escaping (_ fontStyle: String) -> ()) {
self.fontStyle = style
self.closure = closure
}
}
internal class DynamicTypeManager {
// Dynamic Type Manager (Singleton)
static let dynamicTypeManager = DynamicTypeManager()
// Reference to the DynamicTypeManager shared instance
class var shared: DynamicTypeManager {
return dynamicTypeManager
}
// Map table holds a weak reference to registered controls
var registeredElements: NSMapTable<AnyObject, AnyObject>
init () {
self.registeredElements = NSMapTable.weakToStrongObjects()
// Register the Dynamic Type Manager for Dynamic Type Change notifications
NotificationCenter.default.addObserver(forName: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil, queue: nil) { (note) -> Void in
let registeredElements = self.registeredElements
for (element) in registeredElements.keyEnumerator() {
guard let control = element as? UIView else { return }
guard let controlValues = registeredElements.object(forKey: control) as? DynamicTypeControlValues else { return }
if let closure = controlValues.closure {
closure(controlValues.fontStyle)
}else{
if let keyPath = controlValues.fontKeyPath {
control.setValue(UIFont.preferredFont(forTextStyle: UIFontTextStyle(rawValue: controlValues.fontStyle)),
forKeyPath: keyPath)
}else {
// Woops
}
}
// Resize the control to fit
control.sizeToFit()
}
}
}
// Register a UI control
func registerControl(control: UIView, fontKeyPath: String) {
// Get the control's current font and derive it's style
guard let font = control.value(forKeyPath: fontKeyPath) as? UIFont else { return }
guard let fontStyle = self.fontStyleFor(font, view: control) else { return }
// Register the control along with its font keypath and font style
self.registeredElements.setObject(DynamicTypeControlValues(fontKeyPath: fontKeyPath, fontStyle: fontStyle as String),
forKey: control)
}
// Register a UI control with a closure for callback
func registerControl(control: UIView, font: UIFont, withClosure:@escaping (_ fontStyle: String) -> ()){
guard let fontStyle = fontStyleFor(font, view: control) else { return }
// Register the control along with its font style
self.registeredElements.setObject(DynamicTypeControlValues(style: fontStyle, closure: withClosure), forKey: control)
}
// Returns true if the specified control is registered
func isControlRegistered(control: UIView) -> Bool {
return self.registeredElements.object(forKey: control) != nil
}
// Returns the font style for the specified font
func fontStyleFor(_ font: UIFont, view: UIView) -> String? {
var fontStyle: String?
for style in textStyles {
if font.isEqual(UIFont.preferredFont(forTextStyle: style)) {
fontStyle = style.rawValue
break
}
}
guard let _ = fontStyle else {
print("/!\\ [ACCESSIBILITY ERROR] You must specify a style rather than a specific font type for control: \n \(view)")
return nil
}
return fontStyle
}
deinit {
// Remove the Dynamic Type Manager from notifications
NotificationCenter.default.removeObserver(self)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment