Commit 77a779df by Daniel Jonathan Committed by GitHub

Merge pull request #1115 from OrkhanAlikhanov/theme

Introducing Theming to Material
parents edab4901 5ee47a0c
...@@ -170,6 +170,7 @@ ...@@ -170,6 +170,7 @@
96E3C39A1D3A1CC20086A024 /* ErrorTextField.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961F18E71CD93E3E008927C5 /* ErrorTextField.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96E3C39A1D3A1CC20086A024 /* ErrorTextField.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961F18E71CD93E3E008927C5 /* ErrorTextField.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96E3C39C1D3A1CC20086A024 /* Offset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 968C99461D377849000074FF /* Offset.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96E3C39C1D3A1CC20086A024 /* Offset.swift in Headers */ = {isa = PBXBuildFile; fileRef = 968C99461D377849000074FF /* Offset.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96F1A5531F24F17A001D8CAF /* TabsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96E09DC71F2287E50000B121 /* TabsController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 96F1A5531F24F17A001D8CAF /* TabsController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96E09DC71F2287E50000B121 /* TabsController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9D00EBB4216675FB00DBCD69 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D00EBB3216675FB00DBCD69 /* Theme.swift */; };
9D054A6520D175AC00D0528D /* Material+UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6320D175AC00D0528D /* Material+UIButton.swift */; }; 9D054A6520D175AC00D0528D /* Material+UIButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6320D175AC00D0528D /* Material+UIButton.swift */; };
9D054A6620D175AC00D0528D /* Material+UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6420D175AC00D0528D /* Material+UILabel.swift */; }; 9D054A6620D175AC00D0528D /* Material+UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6420D175AC00D0528D /* Material+UILabel.swift */; };
9D39A81B20FE8ED100BA8FA1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */; }; 9D39A81B20FE8ED100BA8FA1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */; };
...@@ -292,6 +293,7 @@ ...@@ -292,6 +293,7 @@
96E09DC71F2287E50000B121 /* TabsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsController.swift; sourceTree = "<group>"; }; 96E09DC71F2287E50000B121 /* TabsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabsController.swift; sourceTree = "<group>"; };
96E3C3931D397AE90086A024 /* Material+UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UIView.swift"; sourceTree = "<group>"; }; 96E3C3931D397AE90086A024 /* Material+UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UIView.swift"; sourceTree = "<group>"; };
96F1DC871D654FDF0025F925 /* Material+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+CALayer.swift"; sourceTree = "<group>"; }; 96F1DC871D654FDF0025F925 /* Material+CALayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+CALayer.swift"; sourceTree = "<group>"; };
9D00EBB3216675FB00DBCD69 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
9D054A6320D175AC00D0528D /* Material+UIButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UIButton.swift"; sourceTree = "<group>"; }; 9D054A6320D175AC00D0528D /* Material+UIButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UIButton.swift"; sourceTree = "<group>"; };
9D054A6420D175AC00D0528D /* Material+UILabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UILabel.swift"; sourceTree = "<group>"; }; 9D054A6420D175AC00D0528D /* Material+UILabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UILabel.swift"; sourceTree = "<group>"; };
9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; }; 9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
...@@ -567,6 +569,7 @@ ...@@ -567,6 +569,7 @@
963FBF011D6696AB008F8512 /* Tab */, 963FBF011D6696AB008F8512 /* Tab */,
966ECF2B1CF4C21B00BB0BDF /* Table */, 966ECF2B1CF4C21B00BB0BDF /* Table */,
96090B031D9D709E00709CA6 /* Text */, 96090B031D9D709E00709CA6 /* Text */,
9D00EBB2216675A800DBCD69 /* Theme */,
963FBF001D66964F008F8512 /* Toolbar */, 963FBF001D66964F008F8512 /* Toolbar */,
9626CA951DAB5370003E2611 /* Transition */, 9626CA951DAB5370003E2611 /* Transition */,
96BCB8061CB40FD000C806FE /* Type */, 96BCB8061CB40FD000C806FE /* Type */,
...@@ -760,6 +763,14 @@ ...@@ -760,6 +763,14 @@
name = Animation; name = Animation;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
9D00EBB2216675A800DBCD69 /* Theme */ = {
isa = PBXGroup;
children = (
9D00EBB3216675FB00DBCD69 /* Theme.swift */,
);
name = Theme;
sourceTree = "<group>";
};
9DE84D6E1FF0250E00586C8B /* ButtonGroup */ = { 9DE84D6E1FF0250E00586C8B /* ButtonGroup */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
...@@ -1002,6 +1013,7 @@ ...@@ -1002,6 +1013,7 @@
966C17731F0439F600D3E83C /* Material+MotionAnimation.swift in Sources */, 966C17731F0439F600D3E83C /* Material+MotionAnimation.swift in Sources */,
965E80E51DD4C53300D61E4B /* PulseAnimation.swift in Sources */, 965E80E51DD4C53300D61E4B /* PulseAnimation.swift in Sources */,
9DE84D721FF0252600586C8B /* RadioButtonGroup.swift in Sources */, 9DE84D721FF0252600586C8B /* RadioButtonGroup.swift in Sources */,
9D00EBB4216675FB00DBCD69 /* Theme.swift in Sources */,
965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */, 965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */,
96328B971E05C0BB009A4C90 /* TableView.swift in Sources */, 96328B971E05C0BB009A4C90 /* TableView.swift in Sources */,
965E80F81DD4D59500D61E4B /* ImageCard.swift in Sources */, 965E80F81DD4D59500D61E4B /* ImageCard.swift in Sources */,
......
...@@ -20,6 +20,7 @@ open class BaseIconLayerButton: Button { ...@@ -20,6 +20,7 @@ open class BaseIconLayerButton: Button {
open override var isSelected: Bool { open override var isSelected: Bool {
didSet { didSet {
iconLayer.setSelected(isSelected, animated: false) iconLayer.setSelected(isSelected, animated: false)
updatePulseColor()
} }
} }
...@@ -84,6 +85,7 @@ open class BaseIconLayerButton: Button { ...@@ -84,6 +85,7 @@ open class BaseIconLayerButton: Button {
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
layer.addSublayer(iconLayer) layer.addSublayer(iconLayer)
iconLayer.prepare()
contentHorizontalAlignment = .left // default was .center contentHorizontalAlignment = .left // default was .center
reloadImage() reloadImage()
} }
...@@ -122,7 +124,7 @@ open class BaseIconLayerButton: Button { ...@@ -122,7 +124,7 @@ open class BaseIconLayerButton: Button {
/// ///
/// This property affects `intrinsicContentSize` and `sizeThatFits(_:)` /// This property affects `intrinsicContentSize` and `sizeThatFits(_:)`
/// Use `iconEdgeInsets` to set margins. /// Use `iconEdgeInsets` to set margins.
open var iconSize: CGFloat = 16 { open var iconSize: CGFloat = 18 {
didSet { didSet {
reloadImage() reloadImage()
} }
...@@ -136,12 +138,24 @@ open class BaseIconLayerButton: Button { ...@@ -136,12 +138,24 @@ open class BaseIconLayerButton: Button {
/// ///
/// You can use `iconSize` and this property, or `titleEdgeInsets` and `contentEdgeInsets` to position /// You can use `iconSize` and this property, or `titleEdgeInsets` and `contentEdgeInsets` to position
/// the icon however you want. /// the icon however you want.
/// For negative values, behavior is undefined. Default is `5.0` for all four margins /// For negative values, behavior is undefined. Default is `8.0` for all four margins
open var iconEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5) { open var iconEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) {
didSet { didSet {
reloadImage() reloadImage()
} }
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
setIconColor(theme.secondary, for: .selected)
setIconColor(theme.onSurface.withAlphaComponent(0.38), for: .normal)
titleColor = theme.onSurface.withAlphaComponent(0.60)
selectedPulseColor = theme.secondary
normalPulseColor = theme.onSurface
updatePulseColor()
}
/// This might be considered as a hackish way, but it's just manipulation /// This might be considered as a hackish way, but it's just manipulation
...@@ -159,6 +173,18 @@ open class BaseIconLayerButton: Button { ...@@ -159,6 +173,18 @@ open class BaseIconLayerButton: Button {
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
self.image = image self.image = image
} }
/// Pulse color for selected state.
open var selectedPulseColor = Color.white
/// Pulse color for normal state.
open var normalPulseColor = Color.white
}
private extension BaseIconLayerButton {
func updatePulseColor() {
pulseColor = isSelected ? selectedPulseColor : normalPulseColor
}
} }
// MARK: - BaseIconLayer // MARK: - BaseIconLayer
...@@ -185,16 +211,6 @@ internal class BaseIconLayer: CALayer { ...@@ -185,16 +211,6 @@ internal class BaseIconLayer: CALayer {
} }
} }
override init() {
super.init()
prepare()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
func prepare() { func prepare() {
normalColor = { normalColor }() // calling didSet normalColor = { normalColor }() // calling didSet
selectedColor = { selectedColor }() // calling didSet selectedColor = { selectedColor }() // calling didSet
......
...@@ -51,7 +51,7 @@ private class MaterialTabBar: UITabBar { ...@@ -51,7 +51,7 @@ private class MaterialTabBar: UITabBar {
} }
} }
open class BottomNavigationController: UITabBarController { open class BottomNavigationController: UITabBarController, Themeable {
/// A Boolean that controls if the swipe feature is enabled. /// A Boolean that controls if the swipe feature is enabled.
open var isSwipeEnabled = true { open var isSwipeEnabled = true {
didSet { didSet {
...@@ -168,6 +168,21 @@ open class BottomNavigationController: UITabBarController { ...@@ -168,6 +168,21 @@ open class BottomNavigationController: UITabBarController {
prepareTabBar() prepareTabBar()
isSwipeEnabled = true isSwipeEnabled = true
isMotionEnabled = true isMotionEnabled = true
applyCurrentTheme()
}
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
tabBar.tintColor = theme.secondary
tabBar.barTintColor = theme.background
tabBar.dividerColor = theme.onSurface.withAlphaComponent(0.12)
if #available(iOS 10.0, *) {
tabBar.unselectedItemTintColor = theme.onSurface.withAlphaComponent(0.54)
}
} }
} }
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
import UIKit import UIKit
import Motion import Motion
open class Button: UIButton, Pulseable, PulseableLayer { open class Button: UIButton, Pulseable, PulseableLayer, Themeable {
/** /**
A CAShapeLayer used to manage elements that would be affected by A CAShapeLayer used to manage elements that would be affected by
the clipToBounds property of the backing layer. For example, this the clipToBounds property of the backing layer. For example, this
...@@ -195,8 +195,8 @@ open class Button: UIButton, Pulseable, PulseableLayer { ...@@ -195,8 +195,8 @@ open class Button: UIButton, Pulseable, PulseableLayer {
*/ */
public init(image: UIImage?, tintColor: UIColor = Color.blue.base) { public init(image: UIImage?, tintColor: UIColor = Color.blue.base) {
super.init(frame: .zero) super.init(frame: .zero)
prepare(with: image, tintColor: tintColor)
prepare() prepare()
prepare(with: image, tintColor: tintColor)
} }
/** /**
...@@ -206,8 +206,8 @@ open class Button: UIButton, Pulseable, PulseableLayer { ...@@ -206,8 +206,8 @@ open class Button: UIButton, Pulseable, PulseableLayer {
*/ */
public init(title: String?, titleColor: UIColor = Color.blue.base) { public init(title: String?, titleColor: UIColor = Color.blue.base) {
super.init(frame: .zero) super.init(frame: .zero)
prepare(with: title, titleColor: titleColor)
prepare() prepare()
prepare(with: title, titleColor: titleColor)
} }
open override func layoutSubviews() { open override func layoutSubviews() {
...@@ -281,7 +281,14 @@ open class Button: UIButton, Pulseable, PulseableLayer { ...@@ -281,7 +281,14 @@ open class Button: UIButton, Pulseable, PulseableLayer {
contentScaleFactor = Screen.scale contentScaleFactor = Screen.scale
prepareVisualLayer() prepareVisualLayer()
preparePulse() preparePulse()
applyCurrentTheme()
} }
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) { }
} }
extension Button { extension Button {
......
...@@ -31,6 +31,12 @@ open class CheckButton: BaseIconLayerButton { ...@@ -31,6 +31,12 @@ open class CheckButton: BaseIconLayerButton {
guard !isAnimating else { return } guard !isAnimating else { return }
setSelected(!isSelected, animated: true) setSelected(!isSelected, animated: true)
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
checkmarkColor = theme.onSecondary
}
} }
internal class CheckBoxLayer: BaseIconLayer { internal class CheckBoxLayer: BaseIconLayer {
...@@ -86,6 +92,7 @@ internal class CheckBoxLayer: BaseIconLayer { ...@@ -86,6 +92,7 @@ internal class CheckBoxLayer: BaseIconLayer {
if isSelected { if isSelected {
borderLayer.borderWidth = borderLayerNormalBorderWidth borderLayer.borderWidth = borderLayerNormalBorderWidth
} else { } else {
borderLayer.borderWidth = 0
borderLayer.backgroundColor = (isEnabled ? normalColor : disabledColor).cgColor borderLayer.backgroundColor = (isEnabled ? normalColor : disabledColor).cgColor
checkMarkLeftLayer.strokeEnd = 1 checkMarkLeftLayer.strokeEnd = 1
checkMarkRightLayer.strokeEnd = 1 checkMarkRightLayer.strokeEnd = 1
......
...@@ -35,7 +35,7 @@ public enum EditorPlaceholderAnimation { ...@@ -35,7 +35,7 @@ public enum EditorPlaceholderAnimation {
case hidden case hidden
} }
open class Editor: View { open class Editor: View, Themeable {
/// Reference to textView. /// Reference to textView.
public let textView = TextView() public let textView = TextView()
...@@ -173,11 +173,14 @@ open class Editor: View { ...@@ -173,11 +173,14 @@ open class Editor: View {
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
backgroundColor = nil
prepareDivider() prepareDivider()
prepareTextView() prepareTextView()
preparePlaceholderLabel() preparePlaceholderLabel()
prepareDetailLabel() prepareDetailLabel()
prepareNotificationHandlers() prepareNotificationHandlers()
applyCurrentTheme()
} }
open override func layoutSubviews() { open override func layoutSubviews() {
...@@ -187,6 +190,21 @@ open class Editor: View { ...@@ -187,6 +190,21 @@ open class Editor: View {
layoutBottomLabel(label: detailLabel, verticalOffset: detailVerticalOffset) layoutBottomLabel(label: detailLabel, verticalOffset: detailVerticalOffset)
} }
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
placeholderActiveColor = theme.secondary
placeholderNormalColor = theme.onSurface.withAlphaComponent(0.38)
dividerActiveColor = theme.secondary
dividerNormalColor = theme.onSurface.withAlphaComponent(0.12)
detailColor = theme.onSurface.withAlphaComponent(0.38)
textView.tintColor = theme.secondary
}
@discardableResult @discardableResult
open override func becomeFirstResponder() -> Bool { open override func becomeFirstResponder() -> Bool {
return textView.becomeFirstResponder() return textView.becomeFirstResponder()
...@@ -196,15 +214,6 @@ open class Editor: View { ...@@ -196,15 +214,6 @@ open class Editor: View {
open override func resignFirstResponder() -> Bool { open override func resignFirstResponder() -> Bool {
return textView.resignFirstResponder() return textView.resignFirstResponder()
} }
open override var inputAccessoryView: UIView? {
get {
return textView.inputAccessoryView
}
set(value) {
textView.inputAccessoryView = value
}
}
} }
......
...@@ -93,4 +93,10 @@ open class ErrorTextField: TextField { ...@@ -93,4 +93,10 @@ open class ErrorTextField: TextField {
super.layoutSubviews() super.layoutSubviews()
layoutBottomLabel(label: errorLabel, verticalOffset: errorVerticalOffset) layoutBottomLabel(label: errorLabel, verticalOffset: errorVerticalOffset)
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
errorColor = theme.error
}
} }
...@@ -36,6 +36,14 @@ open class FABButton: Button { ...@@ -36,6 +36,14 @@ open class FABButton: Button {
depthPreset = .depth1 depthPreset = .depth1
shapePreset = .circle shapePreset = .circle
pulseAnimation = .centerWithBacking pulseAnimation = .centerWithBacking
backgroundColor = .white }
open override func apply(theme: Theme) {
super.apply(theme: theme)
backgroundColor = theme.secondary
titleColor = theme.onSecondary
tintColor = theme.onSecondary
pulseColor = theme.onSecondary
} }
} }
...@@ -35,4 +35,13 @@ open class FlatButton: Button { ...@@ -35,4 +35,13 @@ open class FlatButton: Button {
super.prepare() super.prepare()
cornerRadiusPreset = .cornerRadius1 cornerRadiusPreset = .cornerRadius1
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
backgroundColor = .clear
titleColor = theme.secondary
tintColor = theme.secondary
pulseColor = theme.secondary
}
} }
...@@ -30,9 +30,33 @@ ...@@ -30,9 +30,33 @@
import UIKit import UIKit
public enum IconButtonThemingStyle {
/// Theming when background content is in background color.
case onBackground
/// Theming when background content is in primary color.
case onPrimary
}
open class IconButton: Button { open class IconButton: Button {
/// A reference to IconButtonThemingStyle.
open var themingStyle = IconButtonThemingStyle.onBackground
open override func prepare() { open override func prepare() {
super.prepare() super.prepare()
pulseAnimation = .center pulseAnimation = .center
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
switch themingStyle {
case .onBackground:
tintColor = theme.secondary
pulseColor = theme.secondary
case .onPrimary:
tintColor = theme.onPrimary
pulseColor = theme.onPrimary
}
}
} }
...@@ -62,3 +62,66 @@ public extension UIColor { ...@@ -62,3 +62,66 @@ public extension UIColor {
self.init(argb: (0xff000000 as UInt32) | rgb) self.init(argb: (0xff000000 as UInt32) | rgb)
} }
} }
internal extension UIColor {
/// A tuple of the rgba components.
var components: (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat) {
var r: CGFloat = 0
var g: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
getRed(&r, green: &g, blue: &b, alpha: &a)
return (r, g, b, a)
}
/**
Blends given coverColor over this color.
- Parameter with coverColor: A UIColor.
- Returns: Resultant color of blending.
*/
func blend(with coverColor: UIColor) -> UIColor {
/// Blends channels according to https://en.wikipedia.org/wiki/Alpha_compositing (see `over` operator).
func blendChannel(value: CGFloat, bValue: CGFloat, alpha: CGFloat, bAlpha: CGFloat) -> CGFloat {
return ((1 - alpha) * bValue * bAlpha + alpha * value) / (alpha + bAlpha * (1 - alpha))
}
let (r, g, b, a) = coverColor.components
let (bR, bG, bB, bA) = components
let newR = blendChannel(value: r, bValue: bR, alpha: a, bAlpha: bA)
let newG = blendChannel(value: g, bValue: bG, alpha: a, bAlpha: bA)
let newB = blendChannel(value: b, bValue: bB, alpha: a, bAlpha: bA)
let newA = a + bA * (1 - a)
return UIColor(red: newR, green: newG, blue: newB, alpha: newA)
}
/**
Adjusts brightness of the color by given value.
- Parameter by value: A CGFloat value.
- Returns: Adjusted color.
*/
func adjustingBrightness(by value: CGFloat) -> UIColor {
var h: CGFloat = 0
var s: CGFloat = 0
var b: CGFloat = 0
var a: CGFloat = 0
getHue(&h, saturation: &s, brightness: &b, alpha: &a)
return UIColor(hue: h, saturation: s, brightness: (b + value).clamp(0, 1), alpha: 1)
}
/// A lighter version of the color.
var lighter: UIColor {
return adjustingBrightness(by: 0.1)
}
/// A darker version of the color.
var darker: UIColor {
return adjustingBrightness(by: -0.1)
}
}
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
import UIKit import UIKit
open class NavigationBar: UINavigationBar { open class NavigationBar: UINavigationBar, Themeable {
/// Will layout the view. /// Will layout the view.
open var willLayout: Bool { open var willLayout: Bool {
return 0 < bounds.width && 0 < bounds.height && nil != superview return 0 < bounds.width && 0 < bounds.height && nil != superview
...@@ -168,7 +168,28 @@ open class NavigationBar: UINavigationBar { ...@@ -168,7 +168,28 @@ open class NavigationBar: UINavigationBar {
let image = UIImage() let image = UIImage()
shadowImage = image shadowImage = image
setBackgroundImage(image, for: .default) setBackgroundImage(image, for: .default)
backgroundColor = .white applyCurrentTheme()
}
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
backgroundColor = theme.primary
items?.forEach {
apply(theme: theme, to: $0)
}
}
/**
Applies the given theme to the navigation item.
- Parameter theme: A Theme.
- Parameter to item: A UINavigationItem.
*/
private func apply(theme: Theme, to item: UINavigationItem) {
Theme.apply(theme: theme, to: item.toolbar)
item.toolbar.backgroundColor = .clear
} }
} }
...@@ -182,8 +203,11 @@ internal extension NavigationBar { ...@@ -182,8 +203,11 @@ internal extension NavigationBar {
return return
} }
if isThemingEnabled {
apply(theme: .current, to: item)
}
let toolbar = item.toolbar let toolbar = item.toolbar
toolbar.backgroundColor = .clear
toolbar.interimSpace = interimSpace toolbar.interimSpace = interimSpace
toolbar.contentEdgeInsets = contentEdgeInsets toolbar.contentEdgeInsets = contentEdgeInsets
......
...@@ -35,6 +35,14 @@ open class RaisedButton: Button { ...@@ -35,6 +35,14 @@ open class RaisedButton: Button {
super.prepare() super.prepare()
depthPreset = .depth1 depthPreset = .depth1
cornerRadiusPreset = .cornerRadius1 cornerRadiusPreset = .cornerRadius1
backgroundColor = .white }
open override func apply(theme: Theme) {
super.apply(theme: theme)
backgroundColor = theme.secondary
titleColor = theme.onSecondary
pulseColor = theme.onSecondary
tintColor = theme.onSecondary
} }
} }
...@@ -120,6 +120,12 @@ open class StatusBarController: TransitionController { ...@@ -120,6 +120,12 @@ open class StatusBarController: TransitionController {
super.prepare() super.prepare()
prepareStatusBar() prepareStatusBar()
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
statusBar.backgroundColor = theme.primary.darker
}
} }
fileprivate extension StatusBarController { fileprivate extension StatusBarController {
......
...@@ -30,12 +30,6 @@ ...@@ -30,12 +30,6 @@
import UIKit import UIKit
@objc(SwitchStyle)
public enum SwitchStyle: Int {
case light
case dark
}
@objc(SwitchState) @objc(SwitchState)
public enum SwitchState: Int { public enum SwitchState: Int {
case on case on
...@@ -59,7 +53,7 @@ public protocol SwitchDelegate { ...@@ -59,7 +53,7 @@ public protocol SwitchDelegate {
func switchDidChangeState(control: Switch, state: SwitchState) func switchDidChangeState(control: Switch, state: SwitchState)
} }
open class Switch: UIControl { open class Switch: UIControl, Themeable {
/// Will layout the view. /// Will layout the view.
open var willLayout: Bool { open var willLayout: Bool {
return 0 < bounds.width && 0 < bounds.height && nil != superview return 0 < bounds.width && 0 < bounds.height && nil != superview
...@@ -200,32 +194,6 @@ open class Switch: UIControl { ...@@ -200,32 +194,6 @@ open class Switch: UIControl {
} }
} }
/// Switch style.
open var switchStyle = SwitchStyle.dark {
didSet {
switch switchStyle {
case .light:
buttonOnColor = Color.blue.darken2
trackOnColor = Color.blue.lighten3
buttonOffColor = Color.blueGrey.lighten4
trackOffColor = Color.grey.lighten2
buttonOnDisabledColor = Color.grey.lighten2
trackOnDisabledColor = Color.grey.lighten2
buttonOffDisabledColor = Color.grey.lighten2
trackOffDisabledColor = Color.grey.lighten2
case .dark:
buttonOnColor = Color.blue.lighten1
trackOnColor = Color.blue.lighten2.withAlphaComponent(0.5)
buttonOffColor = Color.grey.lighten2
trackOffColor = Color.blueGrey.lighten4.withAlphaComponent(0.5)
buttonOnDisabledColor = Color.grey.darken3
trackOnDisabledColor = Color.grey.lighten1.withAlphaComponent(0.2)
buttonOffDisabledColor = Color.grey.darken3
trackOffDisabledColor = Color.grey.lighten1.withAlphaComponent(0.2)
}
}
}
/// Switch size. /// Switch size.
open var switchSize = SwitchSize.medium { open var switchSize = SwitchSize.medium {
didSet { didSet {
...@@ -287,13 +255,12 @@ open class Switch: UIControl { ...@@ -287,13 +255,12 @@ open class Switch: UIControl {
- Parameter style: A SwitchStyle value. - Parameter style: A SwitchStyle value.
- Parameter size: A SwitchSize value. - Parameter size: A SwitchSize value.
*/ */
public init(state: SwitchState = .off, style: SwitchStyle = .dark, size: SwitchSize = .medium) { public init(state: SwitchState = .off, size: SwitchSize = .medium) {
track = UIView() track = UIView()
button = FABButton() button = FABButton()
super.init(frame: .zero) super.init(frame: .zero)
prepare() prepare()
prepareSwitchState(state: state) prepareSwitchState(state: state)
prepareSwitchStyle(style: style)
prepareSwitchSize(size: size) prepareSwitchSize(size: size)
} }
...@@ -356,8 +323,24 @@ open class Switch: UIControl { ...@@ -356,8 +323,24 @@ open class Switch: UIControl {
prepareTrack() prepareTrack()
prepareButton() prepareButton()
prepareSwitchState() prepareSwitchState()
prepareSwitchStyle()
prepareSwitchSize() prepareSwitchSize()
applyCurrentTheme()
}
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
buttonOnColor = theme.secondary
trackOnColor = theme.secondary.withAlphaComponent(0.60)
buttonOffColor = theme.surface.blend(with: theme.onSurface.withAlphaComponent(0.15).blend(with: theme.secondary.withAlphaComponent(0.06)))
trackOffColor = theme.onSurface.withAlphaComponent(0.12)
buttonOnDisabledColor = theme.surface.blend(with: theme.onSurface.withAlphaComponent(0.15))
trackOnDisabledColor = theme.onSurface.withAlphaComponent(0.15)
buttonOffDisabledColor = buttonOnDisabledColor
trackOffDisabledColor = trackOnDisabledColor
} }
} }
...@@ -565,15 +548,6 @@ fileprivate extension Switch { ...@@ -565,15 +548,6 @@ fileprivate extension Switch {
} }
/** /**
Prepares the switchStyle property. This is used mainly to allow
init to set the state value and have an effect.
- Parameter style: The SwitchStyle to set.
*/
func prepareSwitchStyle(style: SwitchStyle = .light) {
switchStyle = style
}
/**
Prepares the switchSize property. This is used mainly to allow Prepares the switchSize property. This is used mainly to allow
init to set the size value and have an effect. init to set the size value and have an effect.
- Parameter size: The SwitchSize to set. - Parameter size: The SwitchSize to set.
......
...@@ -614,6 +614,7 @@ internal extension TabBar { ...@@ -614,6 +614,7 @@ internal extension TabBar {
*/ */
func finishLineTransition(isAnimated: Bool = true) { func finishLineTransition(isAnimated: Bool = true) {
line.motionViewTransition.finish(isAnimated: isAnimated) line.motionViewTransition.finish(isAnimated: isAnimated)
line.transition([])
} }
/** /**
...@@ -622,6 +623,7 @@ internal extension TabBar { ...@@ -622,6 +623,7 @@ internal extension TabBar {
*/ */
func cancelLineTransition(isAnimated: Bool = true) { func cancelLineTransition(isAnimated: Bool = true) {
line.motionViewTransition.cancel(isAnimated: isAnimated) line.motionViewTransition.cancel(isAnimated: isAnimated)
line.transition([])
} }
} }
......
...@@ -39,6 +39,12 @@ public enum TabBarAlignment: Int { ...@@ -39,6 +39,12 @@ public enum TabBarAlignment: Int {
case bottom case bottom
} }
public enum TabBarThemingStyle {
case auto
case primary
case secondary
}
extension UIViewController { extension UIViewController {
/// TabItem reference. /// TabItem reference.
@objc @objc
...@@ -161,10 +167,14 @@ open class TabsController: TransitionController { ...@@ -161,10 +167,14 @@ open class TabsController: TransitionController {
/// The tabBar alignment. /// The tabBar alignment.
open var tabBarAlignment = TabBarAlignment.bottom { open var tabBarAlignment = TabBarAlignment.bottom {
didSet { didSet {
updateTabBarAlignment()
layoutSubviews() layoutSubviews()
} }
} }
/// The tabBar theming style.
open var tabBarThemingStyle = TabBarThemingStyle.auto
/** /**
A UIPanGestureRecognizer property internally used for the interactive A UIPanGestureRecognizer property internally used for the interactive
swipe. swipe.
...@@ -218,11 +228,55 @@ open class TabsController: TransitionController { ...@@ -218,11 +228,55 @@ open class TabsController: TransitionController {
prepareTabBar() prepareTabBar()
prepareTabItems() prepareTabItems()
prepareSelectedIndexViewController() prepareSelectedIndexViewController()
applyCurrentTheme()
} }
open override func transition(to viewController: UIViewController, completion: ((Bool) -> Void)?) { open override func transition(to viewController: UIViewController, completion: ((Bool) -> Void)?) {
transition(to: viewController, isTriggeredByUserInteraction: false, completion: completion) transition(to: viewController, isTriggeredByUserInteraction: false, completion: completion)
} }
open override func apply(theme: Theme) {
super.apply(theme: theme)
switch tabBarThemingStyle {
case .auto where (parent is NavigationController || parent is ToolbarController) && tabBarAlignment == .top:
fallthrough
case .primary:
applyPrimary(theme: theme)
default:
applySecondary(theme: theme)
}
}
}
private extension TabsController {
/**
Applies theming taking primary color as base.
- Parameter theme: A Theme
*/
func applyPrimary(theme: Theme) {
tabBar.lineColor = theme.onPrimary.withAlphaComponent(0.68)
tabBar.backgroundColor = theme.primary
tabBar.setTabItemsColor(theme.onPrimary, for: .normal)
tabBar.setTabItemsColor(theme.onPrimary, for: .selected)
tabBar.setTabItemsColor(theme.onPrimary, for: .highlighted)
tabBar.isDividerHidden = true
}
/**
Applies theming taking secondary color as base.
- Parameter theme: A Theme
*/
func applySecondary(theme: Theme) {
tabBar.lineColor = theme.secondary
tabBar.backgroundColor = theme.background
tabBar.setTabItemsColor(theme.onSurface.withAlphaComponent(0.60), for: .normal)
tabBar.setTabItemsColor(theme.secondary, for: .selected)
tabBar.setTabItemsColor(theme.secondary, for: .highlighted)
tabBar.dividerColor = theme.onSurface.withAlphaComponent(0.12)
}
} }
fileprivate extension TabsController { fileprivate extension TabsController {
...@@ -273,9 +327,14 @@ fileprivate extension TabsController { ...@@ -273,9 +327,14 @@ fileprivate extension TabsController {
/// Prepares the TabBar. /// Prepares the TabBar.
func prepareTabBar() { func prepareTabBar() {
tabBar.lineAlignment = .bottom == tabBarAlignment ? .top : .bottom
tabBar._delegate = self tabBar._delegate = self
view.addSubview(tabBar) view.addSubview(tabBar)
updateTabBarAlignment()
}
func updateTabBarAlignment() {
tabBar.lineAlignment = .bottom == tabBarAlignment ? .top : .bottom
tabBar.dividerAlignment = .bottom == tabBarAlignment ? .top : .bottom
} }
/// Prepares the `tabBar.tabItems`. /// Prepares the `tabBar.tabItems`.
......
...@@ -63,7 +63,7 @@ public protocol TextFieldDelegate: UITextFieldDelegate { ...@@ -63,7 +63,7 @@ public protocol TextFieldDelegate: UITextFieldDelegate {
optional func textField(textField: TextField, didClear text: String?) optional func textField(textField: TextField, didClear text: String?)
} }
open class TextField: UITextField { open class TextField: UITextField, Themeable {
/// Minimum TextField text height. /// Minimum TextField text height.
private let minimumTextHeight: CGFloat = 32 private let minimumTextHeight: CGFloat = 32
...@@ -459,6 +459,25 @@ open class TextField: UITextField { ...@@ -459,6 +459,25 @@ open class TextField: UITextField {
prepareTargetHandlers() prepareTargetHandlers()
prepareTextAlignment() prepareTextAlignment()
prepareRightView() prepareRightView()
applyCurrentTheme()
}
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
placeholderActiveColor = theme.secondary
placeholderNormalColor = theme.onSurface.withAlphaComponent(0.38)
leftViewActiveColor = theme.secondary
leftViewNormalColor = theme.onSurface.withAlphaComponent(0.38)
dividerActiveColor = theme.secondary
dividerNormalColor = theme.onSurface.withAlphaComponent(0.12)
detailColor = theme.onSurface.withAlphaComponent(0.38)
textColor = theme.onSurface.withAlphaComponent(0.87)
} }
} }
......
...@@ -87,7 +87,7 @@ public protocol TextViewDelegate : UITextViewDelegate { ...@@ -87,7 +87,7 @@ public protocol TextViewDelegate : UITextViewDelegate {
optional func textView(textView: TextView, didProcessEditing textStorage: TextStorage, text: String, range: NSRange) optional func textView(textView: TextView, didProcessEditing textStorage: TextStorage, text: String, range: NSRange)
} }
open class TextView: UITextView { open class TextView: UITextView, Themeable {
/// A boolean indicating whether the text is empty. /// A boolean indicating whether the text is empty.
open var isEmpty: Bool { open var isEmpty: Bool {
return 0 == text?.utf16.count return 0 == text?.utf16.count
...@@ -289,6 +289,7 @@ open class TextView: UITextView { ...@@ -289,6 +289,7 @@ open class TextView: UITextView {
prepareNotificationHandlers() prepareNotificationHandlers()
prepareRegularExpression() prepareRegularExpression()
preparePlaceholderLabel() preparePlaceholderLabel()
applyCurrentTheme()
} }
open override var contentSize: CGSize { open override var contentSize: CGSize {
...@@ -354,6 +355,15 @@ open class TextView: UITextView { ...@@ -354,6 +355,15 @@ open class TextView: UITextView {
super.paste(sender) super.paste(sender)
fixTypingFont() fixTypingFont()
} }
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
textColor = theme.onSurface.withAlphaComponent(0.87)
placeholderColor = theme.onSurface.withAlphaComponent(0.38)
}
} }
fileprivate extension TextView { fileprivate extension TextView {
......
/*
* Copyright (C) 2015 - 2018, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
import Motion
public protocol Themeable: class {
/**
Applies given theme.
- Parameter theme: A Theme.
*/
func apply(theme: Theme)
/// A boolean indicating if theming is enabled.
var isThemingEnabled: Bool { get set }
}
public struct Theme: Hashable {
/// The color displayed most frequently across the app.
public var primary = Color.blue.darken2
/// Accent color for some components such as FABMenu.
public var secondary = Color.blue.base
/// Background color for view controllers and some components.
public var background = Color.white
/// Background color for components such as cards, and dialogs.
public var surface = Color.white
/// Error color for components such as ErrorTextField.
public var error = Color.red.base
/// Text and iconography color to be used on primary color.
public var onPrimary = Color.white
/// Text and iconography color to be used on secondary color.
public var onSecondary = Color.white
/// Text and iconography color to be used on background color.
public var onBackground = Color.black
/// Text and iconography color to be used on surface color.
public var onSurface = Color.black
/// Text and iconography color to be used on error color.
public var onError = Color.white
/// An initializer.
public init() { }
}
public extension Theme {
/// Current theme for Material.
static private(set) var current = Theme.light
/// A light theme.
static var light = Theme()
/// A dark theme.
static var dark: Theme = {
var t = Theme()
t.primary = UIColor(rgb: 0x202020)
t.secondary = Color.teal.base
t.background = UIColor(rgb: 0x303030)
t.surface = t.background
t.onBackground = .white
t.onSurface = .white
return t
}()
}
public extension Theme {
/**
Applies theme to the entire app.
- Parameter theme: A Theme.
*/
static func apply(theme: Theme) {
current = theme
guard let v = Application.rootViewController else {
return
}
apply(theme: theme, to: v)
}
/**
Applies theme to the hierarchy of given view.
- Parameter theme: A Theme.
- Parameter to view: A UIView.
*/
static func apply(theme: Theme, to view: UIView) {
guard !((view as? Themeable)?.isThemingEnabled == false), !view.isProcessed else {
return
}
view.subviews.forEach {
apply(theme: theme, to: $0)
}
(view as? Themeable)?.apply(theme: theme)
}
/**
Applies theme to the hierarchy of given view controller.
- Parameter theme: A Theme.
- Parameter to viewController: A UIViewController.
*/
static func apply(theme: Theme, to viewController: UIViewController) {
guard !((viewController as? Themeable)?.isThemingEnabled == false) else {
return
}
viewController.allChildren.forEach {
apply(theme: theme, to: $0)
$0.view.isProcessed = true
}
apply(theme: theme, to: viewController.view)
viewController.allChildren.forEach {
$0.view.isProcessed = false
}
(viewController as? Themeable)?.apply(theme: theme)
}
/**
Applies provided theme for the components created within the given block
without chaging app's theme.
- Parameter theme: A Theme.
- Parameter for block: A code block.
*/
static func applying(theme: Theme, for execute: () -> Void) {
let v = current
current = theme
execute()
current = v
}
}
/// A memory reference to the isThemingEnabled for Themeable NSObject subclasses.
private var IsThemingEnabledKey: UInt8 = 0
public extension Themeable where Self: NSObject {
/// A boolean indicating if theming is enabled.
var isThemingEnabled: Bool {
get {
return AssociatedObject.get(base: self, key: &IsThemingEnabledKey) {
true
}
}
set(value) {
AssociatedObject.set(base: self, key: &IsThemingEnabledKey, value: value)
}
}
/// Applies current theme to itself if theming is enabled.
internal func applyCurrentTheme() {
guard isThemingEnabled else {
return
}
apply(theme: .current)
}
}
private extension UIViewController {
/// Returns all possible child view controllers.
var allChildren: [UIViewController] {
var all = children
if let v = self as? TabsController {
all += v.viewControllers
}
if let v = presentedViewController, v.presentingViewController === self {
all.append(v)
}
return all
}
}
/// A memory reference to the isProcessed for UIView.
private var IsProcessedKey: UInt8 = 0
private extension UIView {
/// A boolean indicating if view is already themed.
var isProcessed: Bool {
get {
return AssociatedObject.get(base: self, key: &IsProcessedKey) {
false
}
}
set(value) {
AssociatedObject.set(base: self, key: &IsProcessedKey, value: value)
}
}
}
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
import UIKit import UIKit
open class Toolbar: Bar { open class Toolbar: Bar, Themeable {
/// A convenience property to set the titleLabel.text. /// A convenience property to set the titleLabel.text.
@IBInspectable @IBInspectable
open var title: String? { open var title: String? {
...@@ -63,6 +63,24 @@ open class Toolbar: Bar { ...@@ -63,6 +63,24 @@ open class Toolbar: Bar {
@IBInspectable @IBInspectable
public let detailLabel = UILabel() public let detailLabel = UILabel()
open override var leftViews: [UIView] {
didSet {
prepareIconButtons(leftViews)
}
}
open override var centerViews: [UIView] {
didSet {
prepareIconButtons(centerViews)
}
}
open override var rightViews: [UIView] {
didSet {
prepareIconButtons(rightViews)
}
}
/** /**
An initializer that initializes the object with a NSCoder object. An initializer that initializes the object with a NSCoder object.
- Parameter aDecoder: A NSCoder instance. - Parameter aDecoder: A NSCoder instance.
...@@ -129,6 +147,29 @@ open class Toolbar: Bar { ...@@ -129,6 +147,29 @@ open class Toolbar: Bar {
prepareDetailLabel() prepareDetailLabel()
} }
/**
Applies the given theme.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
backgroundColor = theme.primary
(leftViews + rightViews + centerViews).forEach {
guard let v = $0 as? IconButton, v.isThemingEnabled else {
return
}
v.apply(theme: theme)
}
if !((titleLabel as? Themeable)?.isThemingEnabled == false) {
titleLabel.textColor = theme.onPrimary
}
if !((detailLabel as? Themeable)?.isThemingEnabled == false) {
detailLabel.textColor = theme.onPrimary
}
}
/// A reference to titleLabel.textAlignment observation. /// A reference to titleLabel.textAlignment observation.
private var titleLabelTextAlignmentObserver: NSKeyValueObservation! private var titleLabelTextAlignmentObserver: NSKeyValueObservation!
} }
...@@ -152,4 +193,12 @@ private extension Toolbar { ...@@ -152,4 +193,12 @@ private extension Toolbar {
detailLabel.font = RobotoFont.regular(with: 12) detailLabel.font = RobotoFont.regular(with: 12)
detailLabel.textColor = Color.darkText.secondary detailLabel.textColor = Color.darkText.secondary
} }
func prepareIconButtons(_ views: [UIView]) {
views.forEach {
($0 as? IconButton)?.themingStyle = .onPrimary
}
applyCurrentTheme()
}
} }
...@@ -30,7 +30,7 @@ ...@@ -30,7 +30,7 @@
import UIKit import UIKit
open class ViewController: UIViewController { open class ViewController: UIViewController, Themeable {
open override func viewDidLoad() { open override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
prepare() prepare()
...@@ -45,8 +45,8 @@ open class ViewController: UIViewController { ...@@ -45,8 +45,8 @@ open class ViewController: UIViewController {
*/ */
open func prepare() { open func prepare() {
view.clipsToBounds = true view.clipsToBounds = true
view.backgroundColor = .white
view.contentScaleFactor = Screen.scale view.contentScaleFactor = Screen.scale
applyCurrentTheme()
} }
open override func viewWillLayoutSubviews() { open override func viewWillLayoutSubviews() {
...@@ -61,4 +61,12 @@ open class ViewController: UIViewController { ...@@ -61,4 +61,12 @@ open class ViewController: UIViewController {
have a certain need. have a certain need.
*/ */
open func layoutSubviews() { } open func layoutSubviews() { }
/**
Applies given theme to the view controller.
- Parameter theme: A Theme.
*/
open func apply(theme: Theme) {
view.backgroundColor = theme.background
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment