Last active
February 3, 2018 23:21
-
-
Save indragiek/4553de32a0b70976fa2d to your computer and use it in GitHub Desktop.
Swift API for theming on iOS
This file contains hidden or 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
// Created by Indragie on 5/3/15. | |
// Copyright (c) 2015 Indragie Karunaratne. All rights reserved. | |
import UIKit | |
public protocol ThemeType { | |
typealias Target | |
func apply(target: Target) | |
} | |
// This wouldn't work as a Swift-only protocol for some reason. | |
// EXC_BAD_ACCESS{code=EXC_i386_GPFLT} when trying to call Themable.activeThemeGroupChanged() | |
// | |
// For the same reason, `ThemeGroup` is a class instead of a struct. | |
@objc public protocol Themable { | |
func activeThemeGroupChanged(themeGroup: ThemeGroup) | |
} | |
public final class Weak<T: AnyObject>: Equatable { | |
private weak var value: T? | |
public init(_ value: T) { | |
self.value = value | |
} | |
} | |
public func ==<T: AnyObject>(lhs: Weak<T>, rhs: Weak<T>) -> Bool { | |
return lhs.value === rhs.value | |
} | |
public final class ThemeManager { | |
static let sharedInstance = ThemeManager(defaultThemeGroup: ThemeGroup()) | |
public var activeThemeGroup: ThemeGroup { | |
didSet { | |
for observer in observers { | |
if let observer = observer.value { | |
observer.activeThemeGroupChanged(activeThemeGroup) | |
} | |
} | |
} | |
} | |
private var observers = [Weak<Themable>]() | |
public init(defaultThemeGroup: ThemeGroup) { | |
activeThemeGroup = defaultThemeGroup | |
} | |
public func addObserver(observer: Themable) { | |
observers.append(Weak(observer)) | |
observer.activeThemeGroupChanged(activeThemeGroup) | |
} | |
public func removeObserver(observer: Themable) { | |
if let index = find(observers, Weak(observer)) { | |
observers.removeAtIndex(index) | |
} | |
} | |
} | |
public typealias ThemeKey = String | |
@objc public final class ThemeGroup { | |
public var viewThemes: [ThemeKey: ViewTheme] | |
public var labelThemes: [ThemeKey: LabelTheme] | |
public init(viewThemes: [ThemeKey: ViewTheme] = [:], labelThemes: [ThemeKey: LabelTheme] = [:]) { | |
self.viewThemes = viewThemes | |
self.labelThemes = labelThemes | |
} | |
} | |
public struct ViewTheme: ThemeType { | |
private let backgroundColor: UIColor? | |
private let tintColor: UIColor? | |
private let borderColor: UIColor? | |
public init(backgroundColor: UIColor? = nil, tintColor: UIColor? = nil, borderColor: UIColor? = nil) { | |
self.backgroundColor = backgroundColor | |
self.borderColor = borderColor | |
self.tintColor = tintColor | |
} | |
// MARK: ThemeType | |
public func apply(target: UIView) { | |
if let backgroundColor = backgroundColor { | |
target.backgroundColor = backgroundColor | |
} | |
if let tintColor = tintColor { | |
target.tintColor = tintColor | |
} | |
if let borderColor = borderColor?.CGColor { | |
target.layer.borderColor = borderColor | |
} | |
} | |
} | |
public struct LabelTheme: ThemeType { | |
private let textColor: UIColor | |
private let viewTheme: ViewTheme? | |
public init(textColor: UIColor, viewTheme: ViewTheme? = nil) { | |
self.textColor = textColor | |
self.viewTheme = viewTheme | |
} | |
// MARK: ThemeType | |
public func apply(target: UILabel) { | |
target.textColor = textColor | |
if let viewTheme = viewTheme { | |
viewTheme.apply(target) | |
} | |
} | |
} | |
@IBDesignable public class ThemableView: UIView, Themable { | |
@IBInspectable var themeKey: String? | |
private func commonInit() { | |
ThemeManager.sharedInstance.addObserver(self) | |
} | |
init(frame: CGRect, themeKey: String? = nil) { | |
self.themeKey = themeKey | |
super.init(frame: frame) | |
commonInit() | |
} | |
required public init(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
commonInit() | |
} | |
// MARK: Themable | |
public func activeThemeGroupChanged(themeGroup: ThemeGroup) { | |
if let themeKey = themeKey, theme = themeGroup.viewThemes[themeKey] { | |
theme.apply(self) | |
} | |
} | |
} | |
@IBDesignable public class ThemableLabel: UILabel, Themable { | |
@IBInspectable var themeKey: String? | |
private func commonInit() { | |
ThemeManager.sharedInstance.addObserver(self) | |
} | |
init(frame: CGRect, themeKey: String? = nil) { | |
self.themeKey = themeKey | |
super.init(frame: frame) | |
commonInit() | |
} | |
required public init(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
commonInit() | |
} | |
// MARK: Themable | |
public func activeThemeGroupChanged(themeGroup: ThemeGroup) { | |
if let themeKey = themeKey, theme = themeGroup.labelThemes[themeKey] { | |
theme.apply(self) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Download the playground