Created
October 25, 2018 12:02
-
-
Save samrayner/fe25d56858cc7c9d8b37c84d8c65a13b to your computer and use it in GitHub Desktop.
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
// | |
// RunTimeTheme.swift | |
// RunTimeTheme.swift | |
// | |
// Created by Sam Rayner on 24/10/2018. | |
// Copyright © 2018 Sam Rayner. All rights reserved. | |
// | |
import UIKit | |
extension UIAppearance { | |
fileprivate func applyStyle(key: Int) { | |
Theme.delegate?.theme(for: self).apply(key: key, to: self) | |
} | |
} | |
extension UIView { | |
@objc fileprivate dynamic func style(key: Int) { | |
applyStyle(key: key) | |
} | |
} | |
protocol ThemeDelegate: AnyObject { | |
func theme(for appearanceProxy: UIAppearance) -> Theme | |
} | |
protocol ThemeType: AnyObject { | |
static var delegate: ThemeDelegate? { get set } | |
var styleBlocks: [Int : (UIAppearance) -> Void] { get set } | |
func apply(key: Int, to appearanceProxy: UIAppearance) | |
} | |
extension ThemeType { | |
static func enable(delegate: ThemeDelegate) { | |
self.delegate = delegate | |
} | |
private func appearance<T: UIAppearance>(of objectType: T.Type, | |
for traitCollection: UITraitCollection?, | |
within containerTypes: [UIAppearanceContainer.Type]?) -> T { | |
var object: T | |
if let traitCollection = traitCollection, let containerTypes = containerTypes { | |
object = objectType.appearance(for: traitCollection, whenContainedInInstancesOf: containerTypes) | |
} else if let traitCollection = traitCollection { | |
object = objectType.appearance(for: traitCollection) | |
} else if let containerTypes = containerTypes { | |
object = objectType.appearance(whenContainedInInstancesOf: containerTypes) | |
} else { | |
object = objectType.appearance() | |
} | |
return object | |
} | |
func style<T: UIAppearance>(_ objectType: T.Type, | |
for traitCollection: UITraitCollection? = nil, | |
within containerTypes: [UIAppearanceContainer.Type]? = nil, | |
with closure: @escaping (T) -> Void) { | |
let appearanceProxy = appearance(of: objectType, for: traitCollection, within: containerTypes) | |
closure(appearanceProxy) | |
} | |
func styleAtRunTime<T: UIView>(_ viewType: T.Type, | |
for traitCollection: UITraitCollection? = nil, | |
within containerTypes: [UIAppearanceContainer.Type]? = nil, | |
with closure: @escaping (T) -> Void) { | |
let view = appearance(of: viewType, for: traitCollection, within: containerTypes) | |
let key = ThemeKey(objectType: viewType, traitCollection: traitCollection, containerTypes: containerTypes).hashValue | |
view.style(key: key) | |
styleBlocks[key] = { view in | |
guard let view = view as? T else { return } | |
closure(view) | |
} | |
} | |
func apply(key: Int, to appearanceProxy: UIAppearance) { | |
styleBlocks[key]?(appearanceProxy) | |
} | |
} | |
struct ThemeKey: Hashable { | |
let objectTypeString: String | |
let traitCollection: UITraitCollection? | |
let containerTypeStrings: [String]? | |
init(objectType: UIAppearance.Type, traitCollection: UITraitCollection?, containerTypes: [UIAppearanceContainer.Type]?) { | |
objectTypeString = "\(objectType)" | |
self.traitCollection = traitCollection | |
containerTypeStrings = containerTypes?.map { "\($0)" } | |
} | |
} | |
class Theme: ThemeType { | |
static weak var delegate: ThemeDelegate? | |
var styleBlocks: [Int : (UIAppearance) -> Void] = [:] | |
required init() {} | |
} | |
class MyTheme: Theme { | |
required init() { | |
super.init() | |
style(UIView.self) { | |
$0.backgroundColor = .red | |
} | |
styleAtRunTime(UIView.self) { | |
$0.backgroundColor = .green | |
} | |
} | |
} | |
@UIApplicationMain | |
class AppDelegate: UIResponder, UIApplicationDelegate { | |
var window: UIWindow? | |
let theme = MyTheme() | |
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { | |
// Override point for customization after application launch. | |
Theme.enable(delegate: self) | |
return true | |
} | |
} | |
extension AppDelegate: ThemeDelegate { | |
func theme(for appearanceProxy: UIAppearance) -> Theme { | |
return theme | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment