Created
September 26, 2016 16:04
-
-
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
This file contains 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
// | |
// 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