Skip to content

Theming solution for the apple platforms apps based on the SwiftUI environment property wrapper

License

Notifications You must be signed in to change notification settings

nonameplum/ObservableThemeKit

Repository files navigation

ObservableThemeKit Logo

Swift 5.0 Version Platform Carthage Compatible SPM

ObservableThemeKit

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.

Features

  • 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

Example

The example application is the best way to see ObservableThemeKit in action. Simply open the ObservableThemeKit.xcodeproj and run the Example scheme.

Playground

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.

CocoaPods

ObservableThemeKit is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'ObservableThemeKit'

Carthage

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

Swift Package Manager

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.

Manually

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.

Usage

Documentation

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.

Contributing

Contributions are very welcome 🙌

License

ObservableThemeKit
Copyright (c) 2020 plum sliwinski.lukas@gmail.com

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.