ObservableThemeKit framework allows to easily theme an application. It utilizes protocol oriented programming, property wrapper and observable pattern which allows to customize every aspect of a theme specification for your requirements.
- Uses property wrappers to easlily access a defined theme
- Allows to observe theme's style changes (it could be anything, it depends on you, e.g. light to dark transition)
- You can define any style (stylesheet) that will be used in the themes
The example application is the best way to see ObservableThemeKit
in action. Simply open the ObservableThemeKit.xcodeproj
and run the Example
scheme.
The playground beyond the example application allows to quickly check the usage of the framework. Simply open ObservableThemeKit.xcworkspace
and pick ObservableThemeKitPlayground
from the Xcode's Project navigator.
ObservableThemeKit is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'ObservableThemeKit'
Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
To integrate ObservableThemeKit into your Xcode project using Carthage, specify it in your Cartfile
:
github "nonameplum/ObservableThemeKit"
Run carthage update
to build the framework and drag the built ObservableThemeKit.framework
into your Xcode project.
On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase” and add the Framework path as mentioned in Carthage Getting started Step 4, 5 and 6
To integrate using Apple's Swift Package Manager, add the following as a dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/nonameplum/ObservableThemeKit.git", from: "1.0.0")
]
Alternatively navigate to your Xcode project, select Swift Packages
and click the +
icon to search for ObservableThemeKit
.
If you prefer not to use any of the aforementioned dependency managers, you can integrate ObservableThemeKit into your project manually. Simply drag the Sources
Folder into your Xcode project.
The best way is to take a look at the example app and the playground.
The main concept of the framework is around the Theme
protocol:
public protocol Theme {
associatedtype Style
init(stylesheet: Style)
static var `default`: Self { get }
static var stylesheet: Observable<Style> { get }
}
The idea is to provide a Style
:
struct AppStylesheet {
let accentColor: UIColor
}
that will be used by a theme:
struct ViewTheme: Theme {
static let `default`: ViewController.ViewTheme = .init(stylesheet: AppStylesheet())
static let stylesheet: Observable = Observable(AppStylesheet())
let labelColor: UIColor
init(stylesheet: AppStylesheet) {
self.labelColor = stylesheet.accentColor
}
}
The last step is to use the theme using the ObservableTheme
property wrapper which gives you freedom that the theme could be used anywhere e.g.:
class ViewController: UIViewController {
@ObservableTheme var theme: ViewTheme
}
The theme
can be observed when the stylesheet
has changed:
class ViewController: UIViewController {
@ObservableTheme var theme: ViewTheme
override func viewDidLoad() {
super.viewDidLoad()
self.$theme.observe(
owner: self,
handler: { (owner, _) in
owner.setupAppearance()
}
)
}
}
ObservableTheme
provides projectedValue
which is Observable
.
It is important to mention that the ViewTheme
implements the Theme
protocol which means that the struct
needs to provide
default
, stylesheet
static
properties and the constructor init
.
It uses similar concept as SwiftUI's Environment and related to it EnvironmentKey.
It is used by the ObservableTheme
to instantiate the theme, observe the changes of the stylesheet
and instantiate the new theme on every change and put it back via mentioned observable projectedValue
.
Most of the time you will find that you would like to provide a convenience way of the default
and stylesheet
implementation instead as in this example:
struct ViewTheme: Theme {
static let `default`: ViewController.ViewTheme = .init(stylesheet: AppStylesheet())
static let stylesheet: Observable = Observable(AppStylesheet())
...
}
I personally find useful to declare default implementation of the stylesheet
in the extension
:
extension Theme {
static var stylesheet: Observable<Stylesheet> {
return AppStylesheet.shared
}
}
Having that you can then declare a theme:
struct ViewTheme: Theme {
static let `default`: ViewController.ViewTheme = .init(stylesheet: Self.stylesheet.wrappedValue)
...
}
But as you will find in the examples, there is a lot of ways how you can provide the default
and stylesheet
. It is up to you, it might be singleton, global variable, service locator or any other solution that will suit your needs.
Contributions are very welcome 🙌
ObservableThemeKit
Copyright (c) 2020 plum [email protected]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.