-
Notifications
You must be signed in to change notification settings - Fork 3
/
RootStyle.swift
104 lines (87 loc) · 3.2 KB
/
RootStyle.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#if os(iOS) || os(tvOS)
import UIKit
private typealias View = UIView
private let ViewDidMoveToWindowSelector = #selector(View.didMoveToWindow)
#elseif os(OSX)
import Cocoa
private typealias View = NSView
private let ViewDidMoveToWindowSelector = #selector(View.viewDidMoveToWindow)
#endif
public struct RootStyle {
private static var isStyleAppliedKey = "isStyleApplied"
public enum AutoapplyMethod {
case swizzle
@available(OSX, unavailable)
case appearance
}
public enum Failure: Error {
case notOnMainThread
case alreadyInitialized
case autoapplyFailed
}
public private(set) static var style: StyleApplicator?
public static func set(style: StyleApplicator) throws {
try safeguard()
self.style = style
}
/// Apply the root style to `some` object once. Subsequent calls do nothing.
public static func apply(to some: AnyObject) {
if objc_getAssociatedObject(some, &isStyleAppliedKey) == nil {
objc_setAssociatedObject(some, &isStyleAppliedKey, true, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
style?.apply(to: some)
}
}
// Apply the root style to every `UIView` automatically once.
public static func autoapply(style: StyleApplicator, mode: AutoapplyMethod = .swizzle) throws {
try safeguard()
self.style = style
switch mode {
case .swizzle:
do {
try swizzleInstance(View.self, originalSelector: ViewDidMoveToWindowSelector, swizzledSelector: #selector(View.__stylesheet_didMoveToWindow))
} catch {
throw Failure.autoapplyFailed
}
case .appearance:
#if os(iOS) || os(tvOS)
View.appearance().__stylesheet_applyRootStyle()
#endif
}
}
private static func safeguard() throws {
if !Thread.isMainThread {
throw Failure.notOnMainThread
}
if style != nil {
throw Failure.alreadyInitialized
}
}
}
private extension View {
@objc dynamic
func __stylesheet_didMoveToWindow() {
__stylesheet_didMoveToWindow()
RootStyle.apply(to: self)
}
// This method should look like a setter to be compatible with `UIAppearance`.
@objc dynamic
func __stylesheet_applyRootStyle(_: Any? = nil) {
RootStyle.apply(to: self)
}
}
/// Based on http://nshipster.com/method-swizzling/
private func swizzleInstance<T: NSObject>(_ cls: T.Type, originalSelector: Selector, swizzledSelector: Selector) throws {
guard
let originalMethod = class_getInstanceMethod(cls, originalSelector),
let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
else { throw SwizzleError.selectorNotFound }
let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if (didAddMethod) {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
private enum SwizzleError: Error {
case selectorNotFound
}