Skip to content

Instantly share code, notes, and snippets.

@indragiek
Last active February 3, 2018 23:21
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save indragiek/4553de32a0b70976fa2d to your computer and use it in GitHub Desktop.
Save indragiek/4553de32a0b70976fa2d to your computer and use it in GitHub Desktop.
Swift API for theming on iOS
// 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)
}
}
}
@indragiek
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment