Define UI styles in a hot-reloadable yaml or json file

Overview

Stylist 🎨

Build Status Platforms Carthage compatible CocoaPods version license

Stylist lets you define UI styles in a hot-reloadable external yaml or json theme file

  • βœ… Define styles in external theme files
  • βœ… Apply styles programmatically or via Interface Builder
  • βœ… Hotload themes to see results immediately without recompiling
  • βœ… Apply styles to any UIView, UIViewController, UITabBar, and your own custom classes
  • βœ… Apply styles by style names or classes
  • βœ… Apply styles when contained in certain view hierarchies
  • βœ… Swap entire themes on the fly
  • βœ… Built in style properties for all popular UIKit classes
  • βœ… Reference Theme variables for commonly used values
  • βœ… Include styles within other styles
  • βœ… Define custom strongly typed properties and custom parsing to dynamically set any property

Example theme:

variables:
  primaryColor: "DB3B3B" # hex color
  headingFont: Ubuntu # reference loaded font
styles:
  MyApp.MyViewController: # applied to MyViewController class
    view: # access the view
      tintColor: $primaryColor # reference a variable
    navigationBar: # access the navigation bar
      barTintColor: red # use color name
      titleColor: white
  UIButton:
    backgroundImage: buttonBack # set image
    backgroundImage:highlighted: buttonBack-highlighted # for highlighted control state
  MyApp.Section:
    styles: [themed] # include other styles
    axis(horizontal:regular): horizontal # restrict by size class
    axis(horizontal:compact): vertical
  MyApp.Section UIStackView UILabel: # View containment selector
    textAlignment: right # use enums
  primaryButton:
    textColor: "55F" # shorted hex value
    contentEdgeInsets: [10,5] # set simplified UIEdgeInsets
    font(device:iphone): $headingFont:16 # reference font variable and change size
    font(device:ipad): $headingFont:22 # restrict to device
  secondaryButton:
    cornerRadius: 10 # set layer properties
    textColor: customColor # use named color
    font: 20 # use system font
    contentEdgeInsets: 6 # set UIEdgeInsets using a single value
  sectionHeading:
    font: title 2 # use UIFontTextStyle
    font: darGray:0.5 # built in color with alpha
  content:
    font: Arial:content # Use custom font with UIFontTextStyle
  themed: # style is referenced in other styles
    tintColor: $primaryColor

⬇️ Installing

Cocoapods

Add the following to your podfile

pod 'Stylist'

Carthage

Add the following to your Cartfile

github "yonaskolb/Stylist"

βš’ Usage

Make sure to import Stylist

import Stylist

Loading a Theme

To load a Theme use:

Stylist.shared.load(path: pathToFile)

You can load multiple themes, and they will all be applied as long as they have different paths.

You can also load a Theme manually and then add it by name, allowing you to swap themes at runtime.

let theme = try Theme(path: pathToTheme)
Stylist.shared.addTheme(theme, name: "mainTheme")

Setting a Style

Class styles will be applied to UIView when they are added to a superview, and to UIViewController when viewDidLoad() is called.

To set a custom style on a Styleable class, simply set its style property. You can set multiple styles by comma separating them.

Programmatically

myView.style = "myStyle"
otherView.style = "myStyle,otherStyle"

Interface Builder

Styles can be set in Interface Builder in the property inspector

Hot Reloading

You can choose to watch a Theme files which means that whenever that file is changed the styles are reloaded. These changes can also be animated!

Themes can live at a remote url allowing you to update styles remotely.

Hotloading can be very useful while developing, as you can make changes to your styles on the fly without recompiling and see the results animate in instantly! To watch a file simply call watch on stylist and pass in a URL to a local file on disk or a remote url:

Stylist.shared.watch(url: fileOrRemoteURL, animateChanges: true) { error in
  print("An error occurred while loading or parsing the file: \(error)")
}

If an error occurs at any time the parsingError callback will be called with a ThemeError, which will tell you exactly what went wrong including any formatting errors or invalid references. This means if you accidentally save an invalid theme you don't have to worry that your app will blow up.

To stop watching the file, you can call stop() on the FileWatcher that is returned.

Note that if a style property was present and you then remove it, Stylist cannot revert the change so that property will remain in the previous state.

🎨 Theme

A Theme file has a list of variables and a list of styles. Variables can be referenced in styles using $variableName.

Styles are defined by selector, and are a map of properties to values

variables:
  primaryColor: "DB3B3B"
styles:
  primary:
    color: $primaryColor

Style Selectors

Styles are defined using one or more selectors. Selectors can be a class or a style name or both. Custom classes must be prefixed by the module name. Style names must start with a lowercase.

For example:

  • UIButton all UIButtons
  • MyApp.MyView all MyView classes in the MyApp Module
  • UITabBar.primary all tab bars with the primary style
  • primary all styleables with the primary style

There can be multiple selectors separated by a space, which then check if the later selectors are contained in the earlier selectors. This only applies to UIViews and UIViewControllers. The containers don't have to be direct superviews but can be further up the responder chain.

For example, the following style will be applied to any UIButton that is contained within a view with a section style, that is within a UIStackView with the main style, and then within a UINavigationController.

styles:
  UINavigationController UIStackView.main section UIButton:
    font: title3

Styles will be applied in order of specificity, so the more specific a style is (more selectors), the later it will be applied.

Style references

Each style may also have a styles array that is an array of other inherited styles, who's properties will also be applied without overwriting anything.

styles:
  primary:
    styles: [themed]
  themed:
    tintColor: red
    backgroundColor: EEEEEE

View hierarchy styles

Styles can reference the view hierarchy and then style that with its own properties. This is really useful for testing or accessing parts of the view hierarchy easily (UIViewController.view for example)

The sub styles are available on the following types:

  • UIView
    • superview: The superview
    • next: The next sibling view
    • previous: The previous sibling view
    • viewController: The view controller the view belongs to
  • UIViewController
    • view: The root view
    • parent: The parent view controller
    • navigationController: The UINavigationController this is contained in
    • tabBarController: The UITabBarController this is contained in
    • tabBar: The UITabBar for this view controller. Can be accessed on any child view controller
    • navigationBar: The UINavigationBar for this view controller. Can be accessed on any child view controller
styles:
  MyApp.MyViewController:
    view:
      tintColor: red
    navigationBar:
      tintColor: red

Style Context

Style properties can be restricted to a certain context, for example a certain control state or trait collection. This is similar to how CSS media queries work. See Context for more info

styles:
  UIButton.primary:
    backgroundImage: buttonBack
    backgroundImage:highlighted: buttonBack-highlighted
  UIStackView.main:
    axis(horizontal:regular): horizontal
    axis(horizontal:compact): vertical
  title:
    font(device:iphone): $headingFont:16
    font(device:ipad): $headingFont:22

πŸ– Style Properties

Many UIKit views and bar buttons have built in properties that you can set. These can be viewed in Style Properties.

βš™οΈ Custom Properties

Custom properties and parsers can also be added to let you configure anything you wish in a strongly typed way.

To create a StyleProperty pass a name and a generic closure that sets the property. Make sure to provide types for the styleable class and the generic PropertyValue.

// creates a new property that is applies a TextTransform to a MyLabel
// access the property context and value via the PropertyValue
let property = StyleProperty(name: "textTransform") { (view: MyLabel, value: PropertyValue<TextTransform>) in
    view.textTransform = value.value
}

// adds the custom property to Stylist
Stylist.shared.addProperty(property)

The value must conform to StyleValue which is a simple protocol:

public protocol StyleValue {
    associatedtype ParsedType
    static func parse(value: Any) -> ParsedType?
}

The PropertyValue will have a value property containing your parsed value. It also has a context which contains the property context like device type, UIControlState, UIBarMetrics, size classes..etc.

When a theme is loaded or when a style is set on a view, these custom properties will be applied if the view type and property name match.

Many different types of properties are already supported and listed here in Style Property Types

βš™οΈ Custom Styleable class

By default UIView, UIViewController and UIBarItem are styleable. You can make any custom class styleable as well by conforming to the Styleable protocol. The inbuilt Styleable classes automatically call applyStyles, so you will have to do that automatically in your styles setter.

public protocol Styleable: class {
    var styles: [String] { get set }
}

extension Styleable {

    func applyStyles() {
        Stylist.shared.style(self)
    }
}

πŸ‘₯ Attributions

This tool is powered by:

πŸ‘€ Contributions

Pull requests and issues are welcome

πŸ“„ License

Stylist is licensed under the MIT license. See LICENSE for more info.

Comments
  • when open example get Error loading theme: notFound

    when open example get Error loading theme: notFound

    Error loading theme: notFound , when open example

    test on xcode 8.3.3 , iphone 4 , ios 9.3.

    i fix it like this , in AppDelegate didFinishLaunchingWithOptions .

        if let path = Bundle.main.url(forResource: "Style", withExtension: "yaml") {
            Stylist.shared.watch(url: path, animateChanges: true) { error in
                print("Error loading theme:\n\(error)")
            }
    
        }
    

    thanks

    opened by mohammadshalhoob 5
  • Enhancements proposal

    Enhancements proposal

    First of all I love Stylist! You've really made something great here, especially with that live reload. I've got some feedback so far:

    • Styles designable comma separated styles
    • Update Xibs via IBInspectable and prepareForInterfaceBuilder (I already got this working by adding the code that loads the stylist into the stylist_willMove swizzled method. It works as long as you use a designable view).
    • Optimize finding the right theme using a dictionary instead of looping through all styles?
            for theme in themes.values {
                for style in theme.styles {
                    guard style.applies(to: styleable) else { continue }
                    apply(style: style, to: styleable)
                }
            }
    

    I'm curious what your thoughts are on these.

    opened by garsdle 4
  • Stylist breaks my Carthage deps a bit

    Stylist breaks my Carthage deps a bit

    Can't fetch Carthage deps with the following Cartfile:

    github "ReactiveX/RxSwift" ~> 4.4.0
    github "Moya/Moya" ~> 11.0.2
    github "SwifterSwift/SwifterSwift" ~> 1.6.2
    github "SwiftyBeaver/SwiftyBeaver" ~> 1.6.1
    github "adamwaite/Validator" ~> 3.1.1
    github "SnapKit/SnapKit" ~> 4.0.0
    github "yonaskolb/Stylist" 
    github "Skyvive/Swiftstraints" ~> 4.0.0
    github "yonaskolb/Codability" ~> 0.2.1
    github "ReSwift/ReSwift" ~> 4.0.1
    github "Decybel07/L10n-swift" ~> 5.4.1
    

    Probably conflicts with github "jpsim/Yams" "master" or krzysztofzablocki/KZFileWatchers...

    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching ReactiveSwift
    *** Fetching ReactiveSwift
    *** Fetching KZFileWatchers
    *** Fetching ReactiveSwift
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    *** Fetching ReactiveSwift
    *** Fetching KZFileWatchers
    *** Fetching ReactiveSwift
    *** Fetching KZFileWatchers
    *** Fetching KZFileWatchers
    Failed to check out repository into /Users/yura/Library/Caches/org.carthage.CarthageKit/dependencies/Yams: No object named "nn-dylib-versions" exists (Error Domain=CarthageKit.CarthageError Code=24 "(null)")
    
    opened by yuriy-yarosh 3
  • stylist_willMove optimization

    stylist_willMove optimization

    Original

        @objc func stylist_willMove(toSuperview: UIView) {
            stylist_willMove(toSuperview: toSuperview)
            Stylist.shared.style(self)
        }
    

    Should be able to skip the styling if the superview is nil because the view will not be visible

        @objc func stylist_willMove(toSuperview: UIView) {
            stylist_willMove(toSuperview: toSuperview)
            if toSuperview != nil {
                      Stylist.shared.style(self)
            }
        }
    
    opened by wildthink 1
  • Adding Cartfile and updated dependency

    Adding Cartfile and updated dependency

    Added a Carthfile so carthage would build the dependant frameworks. Also updated Yams to a test version which has a correct framework version number.

    I found that if I checked out Stylist and built it in Xcode it would build the dependencies in the Pods directories. But if I built it using Carthage it would not place the framework files in Carthage's Build directory. I think this is a quirk of basing a project on CocoaPods templates. Anyway, the Carthage file is all that's needed to correctly build Stylist and it's dependencies and make them available.

    opened by drekka 1
  • Not working when built using Carthage

    Not working when built using Carthage

    I've tried to include Stylist in my Carthage dependencies. But there are two issues I've found. Firstly Stylist needs a Cartfile like this:

    github "krzysztofzablocki/KZFileWatchers"
    github "jpsim/Yams"
    

    However when I then tried to get Carthage to update, it tells me there is a circular dependency.

    So next I tried adding KZFileWatcher and Yams to my project's Cartfile. It built ok, but when the app started, it checked the frameworks and crashes, reporting that Yams is version 0.0.0 instead of the required 1.0.0.

    So at the moment I can't use Stylist.

    Any ideas?

    opened by drekka 1
  • Cocoapod

    Cocoapod

    Hey love what you are doing with Stylist. I was keeping an eye on StyleKit but it never matured into a framework I would use.

    The issue: pod 'Stylist' isn't working.

    Unable to find a specification for Stylist

    opened by garsdle 1
  • View controller and view hierarchy styles

    View controller and view hierarchy styles

    • [x] support for styling UIViewController
    • [x] support for UIViewController in style containment selectors
    • [x] support for accessing sub objects in a style like view, viewController, tabBar, navigationBar, toolBar, next, previous, superview, and parent
    • [x] made UIView, UIBarItem and UIViewController styles editable in the IB property inspector
    • [x] support for specifying multiple styles in IB by comma separating them
    • [x] added more styleable properties
    opened by yonaskolb 0
  • Performance impact

    Performance impact

    I'm experiencing severe performance reduction in UICollectionViews, even if no cells (or subviews) are styled at all. I'm guessing that this is caused by the swizzling of func willMove(toSuperview newSuperview: UIView?) that will check every view ever moved to a parent to see if a style should be applied? Any input, past experience, or other help of how to work around the performance reduction would be highly appreciated!

    opened by snoozemoose 0
  • [WIP] Mergeable theme from multiple files with hot reloading

    [WIP] Mergeable theme from multiple files with hot reloading

    First of all congrats on this wonderful library.

    I was struggling with very big yml files, and many recurring variables. So I wanted to give it a go to make a feature that could merge yml files before converting it to Theme objects while still sharing variables/styles between files.

    This is still a work in progress but I wanted to get some feedback. Maybe you had some ideas to implement this?

    opened by avalanched 0
  • Enabling Stylist designable

    Enabling Stylist designable

    Using Carthage, Stylist is added to my project as a framework. This means that the @IBDesignable and @IBInspectable annotations are not visible to IB and therefore I'd have to set the styles programmatically.

    Instead I added the following code to the project and thought you might want to provide it as a work-a-round for other people. Just add it to the project where Stylist is being included as a framework.

    import Stylist
    import UIKit
    
    /// Extensions to enable IB
    
    @IBDesignable extension UIView {
        @IBInspectable var styleName: String? {
            get { return style }
            set { style = newValue }
        }
    }
    
    @IBDesignable extension UIViewController {
        @IBInspectable var styleName: String? {
            get { return style }
            set { style = newValue }
        }
    }
    
    @IBDesignable extension UIBarItem {
        @IBInspectable var styleName: String? {
            get { return style }
            set { style = newValue }
        }
    }
    

    Note: I don't use CocoaPods so I'm not sure if it will have the same issue.

    opened by drekka 0
Releases(0.3.0)
  • 0.3.0(Jun 16, 2019)

  • 0.2.2(Nov 8, 2018)

  • 0.2.1(Jul 23, 2018)

  • 0.2.0(Jun 6, 2018)

    Added

    • Added support for styling UIViewController #7
    • Added support for UIViewController in style containment selectors #7
    • Added support for accessing sub objects in a style like view, viewController, tabBar, navigationBar, toolBar, next, previous, superview, and parent #7
    • Made UIView, UIBarItem and UIViewController styles editable in the IB property inspector
    • Added support for specifying multiple styles in IB by comma separating them #6
    • Added more styleable properties
    • Added support for named colors from Asset Catalog #7

    Changed

    • Styles are applied sorted by specificity #5

    Fixed

    • Fixed nested style references

    Commits

    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(May 23, 2018)

Owner
Yonas Kolb
iOS and Swift dev
Yonas Kolb
YamlSwift - Load YAML and JSON documents using Swift

YamlSwift parses a string of YAML document(s) (or a JSON document) and returns a Yaml enum value representing that string.

Behrang Norouzinia 384 Nov 11, 2022
A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles while pushing or popping a view controller for all orientations. And you don't need to write any line of code for it, it all happens automatically.

Zhouqi Mo 3.3k Dec 21, 2022
null 13 Oct 28, 2022
A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles

A drop-in universal library helps you to manage the navigation bar styles and makes transition animations smooth between different navigation bar styles while pushing or popping a view controller for all orientations. And you don't need to write any line of code for it, it all happens automatically.

Zhouqi Mo 3.3k Dec 21, 2022
Capacitor File Opener. The plugin is able to open a file given the mimeType and the file uri

Capacitor File Opener. The plugin is able to open a file given the mimeType and the file uri. This plugin is similar to cordova-plugin-file-opener2 without installation support.

Capacitor Community 32 Dec 21, 2022
Hot reloading as a Swift Package

Yes, HotReloading for Swift & Objective-C The InjectionIII app available as a Swift Package. i.e.: Then you can do this (using the simulator)... To tr

John Holdsworth 368 Dec 27, 2022
Swift-flows - Simplistic hot and cold flow-based reactive observer pattern for Swift… ideal for MVVM architectures

SwiftFlows Simplistic hot and cold flow-based reactive observer pattern for Swif

Tyler Suehr 0 Feb 2, 2022
Hot Reloading for Swift applications!

Inject Hot reloading workflow helper that enables you to save hours of time each week, regardless if you are using UIKit, AppKit or SwiftUI. TLDR: A s

Krzysztof ZabΕ‚ocki 1.4k Dec 25, 2022
A sweet and swifty YAML parser built on LibYAML.

Yams A sweet and swifty YAML parser built on LibYAML. Installation Building Yams requires Xcode 11.x or a Swift 5.1+ toolchain with the Swift Package

JP Simard 930 Jan 4, 2023
Proper YAML support for Objective-C. Based on recommended libyaml.

YAML.framework for Objective-C Based on C LibYAML library (http://pyyaml.org/wiki/LibYAML) by Kirill Simonov. YAML.framework provides support for YAML

Mirek Rusin 236 Aug 29, 2022
Helps you define secure storages for your properties using Swift property wrappers.

?? Secure Property Storage Helps you define secure storages for your properties using Swift property wrappers. ?? Features All keys are hashed using S

Alex RupΓ©rez 443 Jan 4, 2023
OysterKit is a framework that provides a native Swift scanning, lexical analysis, and parsing capabilities. In addition it provides a language that can be used to rapidly define the rules used by OysterKit called STLR

OysterKit A Swift Framework for Tokenizing, Parsing, and Interpreting Languages OysterKit enables native Swift scanning, lexical analysis, and parsing

Swift Studies 178 Sep 16, 2022
A result builder that allows to define shape building closures

ShapeBuilder A result builder implementation that allows to define shape building closures and variables. Problem In SwiftUI, you can end up in a situ

Daniel Peter 47 Dec 2, 2022
Define and chain Closures with Inputs and Outputs

Closure Define and chain Closures with Inputs and Outputs Examples No Scoped State let noStateCount = Closure<String, String> { text in String(repea

Zach Eriksen 3 May 18, 2022
A declarative, thread safe, and reentrant way to define code that should only execute at most once over the lifetime of an object.

SwiftRunOnce SwiftRunOnce allows a developer to mark a block of logic as "one-time" code – code that will execute at most once over the lifetime of an

Thumbtack 8 Aug 17, 2022
A concept to more easily define simple keyframe / multi-step animations in SwiftUI

?? Animate A concept to more easily define simple keyframe / multi-step animations in SwiftUI, without: Defining an @State value for each property to

Seb Jachec 3 Oct 18, 2022
ips2crash is a macOS command line too to convert a .ips file to a legacy .crash log file.

Synopsis ips2crash is a macOS command line too to convert a .ips file to a legacy .crash log file. Motivation It should be possible to read .ips file

null 36 Nov 25, 2022
Edit a file, create a new file, and clone from Bitbucket in under 2 minutes

Edit a file, create a new file, and clone from Bitbucket in under 2 minutes When you're done, you can delete the content in this README and update the

Nikunj Munjiyasara 0 Nov 9, 2021
LinkedLog is a Xcode plugin that includes a Xcode PCH header file template that adds the macros `LLog` and `LLogF` and parses their output to link from the console to the corresponding file and line.

LinkedLog Xcode Plugin LinkedLog is a Xcode plugin that includes a Xcode PCH file template that adds the macros LLog and LLogF. The LLog macro will wo

Julian F. Weinert 22 Nov 14, 2022
An Adobe .ase (Adobe Swatch Exchange File), .aco (Photoshop swatch file) reader/writer package for Swift (macOS, iOS, tvOS, macCatalyst)

ColorPaletteCodable A palette reader/editor/writer package for iOS, macOS, watchOS and tvOS, supporting the following formats Adobe Swatch Exchange (.

Darren Ford 11 Nov 29, 2022