Lightweight and customizable stylesheets for iOS

Overview

Motif

Build Status Carthage compatible Coverage Status

Lightweight and customizable stylesheets for iOS

What can it do?

  • Declare rules defining your app's visual appearance separately from your UI component implementations to promote separation of concerns in your codebase
  • Dynamically change your app's appearance at runtime from a user setting, as an premium feature, or even from the screen's brightness like Tweetbot:

Brightness Theming

  • Reload your app's appearance when you edit your theme files (without rebuilding), saving loads of time when styling your interface:

Live Reloading

Why should I use it?

You have an app. Maybe even a family of apps. You know about CSS, and how it enables web developers to write a set of declarative classes to style elements throughout their site, creating composable interface definitions that are entirely divorced from page content. You'll admit that you're a little jealous that things aren't quite the same on iOS.

To style your app today, maybe you have a MyAppStyle singleton that vends styled interface components that's a dependency of nearly every view controller in your app. Maybe you use Apple's UIAppearance APIs, but you're limited to a frustratingly small subset of the appearance APIs. Maybe you've started to subclass some UIKit classes just to set a few defaults to create some styled components. You know this sucks, but there just isn't a better way to do things in iOS.

Well, things are about to change. Take a look at the example below to see what Motif can do for you:

An example

The following is a simple example of how you'd create a pair of styled buttons with Motif. To follow along, you can either continue reading below or clone this repo and run the ButtonsExample target within Motif.xcworkspace.

The design

Horizontal Layout

Your designer just sent over a spec outlining the style of a couple buttons in your app. Since you'll be using Motif to create these components, that means it's time to create a theme file.

Theme files

A theme file in Motif is just a simple dictionary encoded in a markup file (YAML or JSON). This dictionary can have two types of key/value pairs: classes or constants:

  • Classes: denoted by a leading period (e.g. .Button) and encoded as a key with a value of a nested dictionary, a class is a collection of named properties corresponding to values that together define the style of an element in your interface. Class property values can be of any type, or alternatively a reference to another class or constant.
.Class:
    property: value
  • Constants: denoted by a leading dollar sign (e.g. $RedColor) and encoded as a key-value pair, a constant is a named reference to a value. Constant values can be of any type, or alternatively a reference to another class or constant.
$Constant: value

To create the buttons from the design spec, we've written the following theme file:

Theme.yaml

$RedColor: '#f93d38'
$BlueColor: '#50b5ed'
$FontName: AvenirNext-Regular

.ButtonText:
    fontName: $FontName
    fontSize: 16

.Button:
    borderWidth: 1
    cornerRadius: 5
    contentEdgeInsets: [10, 20]
    tintColor: $BlueColor
    borderColor: $BlueColor
    titleText: .ButtonText

.WarningButton:
    _superclass: .Button
    tintColor: $RedColor
    borderColor: $RedColor

We've started by defining three constants: our button colors as $RedColor and $BlueColor, and our font name as $FontName. We choose to define these values as constants because we want to be able to reference them later in our theme file by their names—just like variables in Less or Sass.

We now declare our first class: .ButtonText. This class describes the attributes of the text within the button—both its font and size. From now on, we'll use this class to declare that we want text to have this specific style.

You'll notice that the value of the fontName property on the .ButtonText class is a reference to the $FontName constant we declared above. As you can probably guess, when we use this class, its font name will have the same value as the $FontName constant. This way, we're able to have one definitive place that our font name exists so we don't end up repeating ourselves.

The last class we declare is our .WarningButton class. This class is exactly like our previous classes except for one key difference: it inherits its properties from the .Button class via the _superclass directive. As such, when the .WarningButton class is applied to an interface component, it will have identical styling to that of .Button for all attributes except tintColor and borderColor, which it overrides to be $RedColor instead.

Property appliers

Next, we'll create the property appliers necessary to apply this theme to our interface elements. Most of the time, Motif is able to figure out how to apply your theme automatically by matching Motif property names to Objective-C property names. However, in the case of some properties, we have to declare how its value should be applied ourselves. To do this, we'll register our necessary theme property appliers in the load method of a few categories.

The first set of property appliers we've created is on UIView:

UIView+Theming.m

+ (void)load {
    [self
        mtf_registerThemeProperty:@"borderWidth"
        requiringValueOfClass:NSNumber.class
        applierBlock:^(NSNumber *width, UIView *view, NSError **error) {
            view.layer.borderWidth = width.floatValue;
            return YES;
        }];

    [self
        mtf_registerThemeProperty:@"borderColor"
        requiringValueOfClass:UIColor.class
        applierBlock:^(UIColor *color, UIView *view, NSError **error) {
            view.layer.borderColor = color.CGColor;
            return YES;
        }];
}

Here we've added two property appliers to UIView: one for borderWidth and another for borderColor. Since UIView doesn't have properties for these attributes, we need property appliers to teach Motif how to apply them to the underlying CALayer.

Applier type safety

If we want to ensure that we always apply property values a of specific type, we can specify that an applier requires a value of a certain class by using requiringValueOfClass: when registering an applier. In the case of the borderWidth property above, we require that its value is of class NSNumber. This way, if we ever accidentally provide a non-number value for a borderWidth property, a runtime exception will be thrown so that we can easily identify and fix our mistake.

Now that we've defined these two properties on UIView, they will be available to apply any other themes with the same properties in the future. As such, whenever we have another class wants to specify a borderColor or borderWidth to a UIView or any of its descendants, these appliers will be able to apply those values as well.

Next up, we're going to add an applier to UILabel to style our text:

UILabel+Theming.m

+ (void)load {
    [self
        mtf_registerThemeProperties:@[
            @"fontName",
            @"fontSize"
        ]
        requiringValuesOfType:@[
            NSString.class,
            NSNumber.class
        ]
        applierBlock:^(NSDictionary<NSString *, id> *properties, UILabel *label, NSError **error) {
            NSString *name = properties[ThemeProperties.fontName];
            CGFloat size = [properties[ThemeProperties.fontSize] floatValue];
            UIFont *font = [UIFont fontWithName:name size:size];

            if (font != nil) {
                label.font = font;
                return YES;
            }

            return [self
                mtf_populateApplierError:error
                withDescriptionFormat:@"Unable to create a font named %@ of size %@", name, @(size)];
        }];
}

Compound property appliers

The above applier is a compound property applier. This means that it's a property applier that requires multiple property values from the theme class for it to be applied. In this case, we need this because we can not create a UIFont object without having both the size and name of the font beforehand.

The final applier that we'll need is on UIButton to style its titleLabel:

UIButton+Theming.m

+ (void)load {
    [self
        mtf_registerThemeProperty:@"titleText"
        requiringValueOfClass:MTFThemeClass.class
        applierBlock:^(MTFThemeClass *class, UIButton *button, NSError **error) {
            return [class applyTo:button.titleLabel error:error];
        }];
}

Properties with MTFThemeClass values

Previously, we created a style applier on UILabel that allows us specify a custom font from a theme class. Thankfully, we're able to use this applier to style our UIButton's titleTabel as well. Since the titleText property on the .Button class is a reference to the .ButtonText class, we know that the value that comes through this applier will be of class MTFThemeClass. MTFThemeClass objects are used to represent classes like .TitleText from theme files, and are able to apply their properties to an object directly. As such, to apply the fontName and fontSize from the .TitleText class to our button's label, we simply invoke applyToObject: on titleLabel with the passed MTFThemeClass.

Putting it all together

NSError *error;
MTFTheme *theme = [MTFTheme themeFromFileNamed:@"Theme" error:&error];
NSAssert(theme != nil, @"Error loading theme %@", error);

[theme applyClassWithName:@"Button" to:saveButton error:NULL];
[theme applyClassWithName:@"WarningButton" to:deleteButton error:NULL];

We now have everything we need to style our buttons to match the spec. To do so, we must instantiate a MTFTheme object from our theme file to access our theme from our code. The best way to do this is to use themeFromFileNamed:, which works just like imageNamed:, but for MTFTheme instead of UIImage.

When we have our MTFTheme, we want to make sure that there were no errors parsing it. We can do so by asserting that our pass-by-reference error is still non-nil. By using NSAssert, we'll get a runtime crash when debugging informing us of our errors (if there were any).

To apply the classes from Theme.yaml to our buttons, all we have to do is invoke the application methods on MTFTheme on our buttons. When we do so, all of the properties from the theme classes are applied to our buttons in just one simple method invocation. This is the power of Motif.

Using Motif in your project

The best way to use Motif in your project is with either Carthage or CocoaPods.

Carthage

Add the following to your Cartfile:

github "EricHoracek/Motif"

CocoaPods

Add the following to your Podfile:

pod 'Motif'

Dynamic theming

One of Motif's most powerful features is its ability to dynamically theme your application's interface at runtime. While adopting dynamic theming is simple, it does require you to use Motif in a slightly different way from the above example.

For an example of dynamic theming in practice, clone this repo and run the DynamicThemingExample target within Motif.xcworkspace.

Dynamic theme appliers

To enable dynamic theming, where you would normally use the theme class application methods on MTFTheme for a single theme, you should instead use the identically-named methods on MTFDynamicThemeApplier when you wish to have more than one theme. This enables you to easily re-apply a new theme to your entire interface just by changing the theme property on your MTFDynamicThemeApplier.

// We're going to default to the light theme
MTFTheme *lightTheme = [MTFTheme themeFromFileNamed:@"LightTheme" error:NULL];

// Create a dynamic theme applier, which we will use to style all of our
// interface elements
MTFDynamicThemeApplier *applier = [[MTFDynamicThemeApplier alloc] initWithTheme:lightTheme];

// Style objects the same way as we did with an MTFTheme instance
[applier applyClassWithName:@"InterfaceElement" to:anInterfaceElement error:NULL];

Later on...

// It's time to switch to the dark theme
MTFTheme *darkTheme = [MTFTheme themeFromFileNamed:@"DarkTheme" error:NULL];

// We now change the applier's theme to the dark theme, which will automatically
// re-apply this new theme to all interface elements that previously had the
// light theme applied to them
[applier setTheme:darkTheme error:NULL];

"Mapping" themes

While you could maintain multiple sets of divergent theme files to create different themes for your app's interface, the preferred (and easiest) way to accomplish dynamic theming is to largely share the same set of theme files across your entire app. An easy way to do this is to create a set of "mapping" theme files that map your root constants from named values describing their appearance to named values describing their function. For example:

Colors.yaml

$RedDarkColor: '#fc3b2f'
$RedLightColor: '#fc8078'

DarkMappings.yaml

$WarningColor: $RedLightColor

LightMappings.yaml

$WarningColor: $RedDarkColor

Buttons.yaml

.Button:
    tintColor: $WarningColor

We've created a single constant, $WarningColor, that will change its value depending on the mapping theme file that we create our MTFTheme instances with. As discussed above, the constant name $WarningColor describes the function of the color, rather than the appearance (e.g. $RedColor). This redefinition of the same constant allows us to us to conditionally redefine what $WarningColor means depending on the theme we're using. As such, we don't have to worry about maintaining multiple definitions of the same .Button class, ensuring that we don't repeat ourselves and keep things clean.

With this pattern, creating our light and dark themes is as simple as:

MTFTheme *lightTheme = *theme = [MTFTheme
    themeFromFileNamed:@[
        @"Colors",
        @"LightMappings",
        @"Buttons"
    ] error:NULL];

MTFTheme *darkTheme = *theme = [MTFTheme
    themeFromFileNamed:@[
        @"Colors",
        @"DarkMappings",
        @"Buttons"
    ] error:NULL];

This way, we only have to create our theme classes one time, rather than once for each theme that we want to add to our app. For a more in-depth look at this pattern, clone this repo and read the source of the DynamicThemingExample target within Motif.xcworkspace.

Generating theme symbols

In the above examples, you might have noticed that Motif uses a lot of stringly types to bridge class and constant names from your theme files into your application code. If you've dealt with a system like this before (Core Data's entity and attribute names come to mind as an example), you know that over time, stringly-typed interfaces can become tedious to maintain. Thankfully, just as there exists Mogenerator to alleviate this problem when using Core Data, there also exists a similar solution for Motif to address this same problem: the Motif CLI.

Motif CLI

Motif ships with a command line interface that makes it easy to ensure that your code is always in sync with your theme files. Let's look at an example of how to use it in your app:

You have a simple YAML theme file, named Buttons.yaml:

$RedColor: '#f93d38'
$BlueColor: '#50b5ed'
$FontName: AvenirNext-Regular

.ButtonText:
    fontName: $FontName
    fontSize: 16

.Button:
    borderWidth: 1
    cornerRadius: 5
    contentEdgeInsets: [10, 20]
    tintColor: $BlueColor
    borderColor: $BlueColor
    titleText: .ButtonText

.WarningButton:
    _superclass: .Button
    tintColor: $RedColor
    borderColor: $RedColor

To generate theme symbols from this theme file, just run:

motif --theme Buttons.yaml

This will generate the a pair of files named ButtonSymbols.{h,m}. ButtonSymbols.h looks like this:

extern NSString * const ButtonsThemeName;

extern const struct ButtonsThemeConstantNames {
    __unsafe_unretained NSString *BlueColor;
    __unsafe_unretained NSString *FontName;
    __unsafe_unretained NSString *RedColor;
} ButtonsThemeConstantNames;

extern const struct ButtonsThemeClassNames {
    __unsafe_unretained NSString *Button;
    __unsafe_unretained NSString *ButtonText;
    __unsafe_unretained NSString *WarningButton;
} ButtonsThemeClassNames;

extern const struct ButtonsThemeProperties {
    __unsafe_unretained NSString *borderColor;
    __unsafe_unretained NSString *borderWidth;
    __unsafe_unretained NSString *contentEdgeInsets;
    __unsafe_unretained NSString *cornerRadius;
    __unsafe_unretained NSString *fontName;
    __unsafe_unretained NSString *fontSize;
    __unsafe_unretained NSString *tintColor;
    __unsafe_unretained NSString *titleText;
} ButtonsThemeProperties;

Now, when you add the above pair of files to your project, you can create and apply themes using these symbols instead:

#import "ButtonSymbols.h"

NSError *error;
MTFTheme *theme = [MTFTheme themeFromFileNamed:ButtonsThemeName error:&error];
NSAssert(theme != nil, @"Error loading theme %@", error);

[theme applyClassWithName:ButtonsThemeClassNames.Button to:saveButton error:NULL];
[theme applyClassWithName:ButtonsThemeClassNames.WarningButton to:deleteButton error:NULL];

As you can see, there's now no more stringly-typing in your application code. To delve further into an example of how to use the Motif CLI, check out any of the examples in Motif.xcworkspace.

Installation

To install the Motif CLI, simply build and run the MotifCLI target within Motif.xcworkspace. This will install the motif CLI to your /usr/local/bin directory.

As a "Run Script" build phase

To automate the symbols generation, just add the following to a run script build phase to your application. This script assumes that all of your theme files end in Theme.yaml, but you can modify this to your liking.

export PATH="$PATH:/usr/local/bin/"
export CLI_TOOL='motif'

which "${CLI_TOOL}"

if [ $? -ne 0  ]; then exit 0; fi

export THEMES_DIR="${SRCROOT}/${PRODUCT_NAME}"

find "${THEMES_DIR}" -name '*Theme.yaml' |  sed 's/^/-t /' | xargs "${CLI_TOOL}" -o "${THEMES_DIR}"

This will ensure that your symbols files are always up to date with your theme files. Just make sure the this run script build phase is before your "Compile Sources" build phase in your project. For an example of this in practice, check out any of the example projects within Motif.xcworkspace.

Live Reload

To enable live reloading, simply replace your MTFDynamicThemeApplier with an MTFLiveReloadThemeApplier when debugging on the iOS Simulator:

#if TARGET_IPHONE_SIMULATOR && defined(DEBUG)

self.themeApplier = [[MTFLiveReloadThemeApplier alloc]
    initWithTheme:theme
    sourceFile:__FILE__];

#else

self.themeApplier = [[MTFDynamicThemeApplier alloc]
    initWithTheme:theme];

#endif

Live reloading will only work on the iOS Simulator, as editable theme source files do not exist on your iOS Device. For a more in-depth look at how to implement live reloading, clone this repo and read the source of the DynamicThemingExample target within Motif.xcworkspace.

Comments
  • Convenience applier categories would be appreciated

    Convenience applier categories would be appreciated

    Support for properties could be included with Motif.

    Example transformers from the readme would be really useful if they were default for UIKit classes:

    + (void)load
    {
        [self
            mtf_registerThemeProperty:@"textColor"
            valueTransformerName:MTFColorFromStringTransformer
            applier:^(UIColor *textColor, UIButton *button) {
                [button setTextColor:textColor forState:UIControlStateNormal];
        }];
    
        [self
            mtf_registerThemeProperties:@[
                @"fontName",
                @"fontSize"
            ] valueTransformersNamesOrRequiredClasses:@[
                [NSString class],
                [NSNumber class]
            ] applier:^(NSDictionary *properties, UIButton *button) {
                NSString *name = properties[@"fontName"];
                CGFloat size = [properties[@"fontSize"] floatValue];
                button.titleLabel.font = [UIFont fontWithName:name size:size];
            }];
    }
    

    I can imaging it as an optional subspec in CocoaPods so that it can be more easily platform-specific, and even ignored. I'm not sure what it would look like via Carthage (a separate framework?).

    What do you think?

    opened by fcanas 14
  • Update for Xcode 8

    Update for Xcode 8

    Warning: This does not build on Xcode 7 anymore when targeting devices, with the error:

    CodeSign error: code signing is required for product type 'Framework' in SDK 'iOS 9.3'

    opened by erichoracek 5
  • Swift: UIEdgeInsets property applier

    Swift: UIEdgeInsets property applier

    How do I add property applier for UIEdgeInsets in Swift? If I understood correctly, Objective-C version of property applier for UIEdgeInsets would be something like this:

    [self mtf_registerThemeProperty:@"titleEdgeInsets" requiringValueOfObjCType:@encode(UIEdgeInsets) applierBlock:^(NSValue *value, UIButton *button) {
            button.titleEdgeInsets = value.UIEdgeInsetsValue;
    }];
    

    But Swift doesn't have @encode compiler directive.

    opened by jsslai 5
  • Naming

    Naming

    /cc @fcanas

    I'm trying to think of the best naming scheme for the project. Right now it's the following:

    • AUTTheme Container of:
      • AUTThemeConstant - Named constant mapped to values
      • AUTThemeClass - Named container that maps named properties to values (The property → value mappings are internally instances of AUTThemeConstant)
    • AUTThemeApplier Responsible for applying a theme to objects, via the applyClassWithName:toObject: method
    • <AUTThemeClassApplicable> Protocol defining a generic interface for applying a AUTThemeClass to an object. A set of these are used internally by AUTThemeApplier to apply a theme to an object
      • AUTThemeClassApplier Applier invoked when a class is applied to an object
      • AUTThemeClassPropertyApplier Applier invoked with a specific property is applied to an object
      • AUTThemeClassPropertiesApplier Applier invoked when a set of properties are applied to an object
    opened by erichoracek 4
  • Mutability

    Mutability

    /cc @fcanas

    I'm unhappy with the current state of mutability in the project

    • [ ] AUTTheme should be entirely immutable to consumers. I want to think of a good syntax for initializing a theme from a set of JSON files that isn't cumbersome to use but is able to provide errors as well. Basically addAttributesFromThemeAtURL:error: should not exist in the public interface, and it instead should occur on an initWith-stye initialization.
    opened by erichoracek 4
  • Allow overriding of existing constants with identical names by registering new constants.

    Allow overriding of existing constants with identical names by registering new constants.

    Great work on the framework. We just started using Motif and really like it. When coming up with internal standards on how best to adopt it we ran into a scenario where we would like the ability to define constants and have the ability to override as needed.

    We need the ability to switch colors for a few items at startup depending on a customer preference. Our original idea was to use load 2 different themes (lets call it Theme A and Theme B).

    Theme A (loads classes and constants from A.json) Theme B (loads A.json and B.json where B.json contains overrides of constant values)

    We were then going to use the MTFDynamicThemeApplier to switch between the two.

    We see that the overriding of existing constants and classes is prohibited in the MTFThemeParser and generates a parsing error if attempted.

    We would love the ability to do it (as described in our above scenario) maybe through a configurable property (so as not to impact current users).

    Would y'all be opposed to to this idea? If not, we are happy to submit a pull request for this.

    opened by kunal30xi 3
  • Add -k option to avoid touching unmodified files.

    Add -k option to avoid touching unmodified files.

    :confused: May not be a reasonable feature.

    Added -k option to CLI so that themes that haven't changed leave the headers unmodified. Without this, it's more of a pain to stop Xcode rebuilding the entire UI code every every build, if you're using a build phase to run the CLI.

    Something similar could be accomplished with a file modification time check, without the disadvantage of building each file completely in memory before output, but I don't expect theme symbols to be huge.

    opened by jlawton 3
  • Issue building Motif with Xcode 12

    Issue building Motif with Xcode 12

    We are currently importing Motif 0.3.8 via Carthage When building with Xcode 12, there is a compilation error on line 434 and line 436 of UIColor+HTMLColors.m

    {
        float f = 0.0;
        if ([self scanFloat:&f]) {
            if ([self scanString:@"%" intoString:NULL]) {
                f *= 0.01;
            } else {
                f *= scale;
            }
            if (value) {
                *value = f;
            }
            return YES;
        }
        return NO;
    }
    
    Compilation error: Implicit conversion when assigning computation result loses floating-point precision: 'double' to 'float'
    opened by kunal30xi 2
  • Speed up theme source observation with deep source directories and theme hierarchies

    Speed up theme source observation with deep source directories and theme hierarchies

    This extracts the expensive operation of iterating through the source observation directory into a method that is only invoked once per observation, rather than invoked once per theme per observation.

    opened by erichoracek 2
  • Incorrect path to code coverage files when executing test suite

    Incorrect path to code coverage files when executing test suite

    When we execute our test suite built against Motif, we are seeing the following output: (I'm assuming this is the path to the code coverage files located on your workstation).

    profiling: /Users/erh/Library/Developer/Xcode/DerivedData/Motif-fymrucbnitjyghdhgxaviyllghzs/Build/Intermediates/Motif.build/Release-iphonesimulator/Motif-iOS.build/Objects-normal/x86_64/NSURL+LastPathComponentWithoutExtension.gcda: cannot open: No such file or directory profiling: /Users/erh/Library/Developer/Xcode/DerivedData/Motif-fymrucbnitjyghdhgxaviyllghzs/Build/Intermediates/Motif.build/Release-iphonesimulator/Motif-iOS.build/Objects-normal/x86_64/NSURL+LastPathComponentWithoutExtension.gcda: cannot open: No such file or directory profiling: /Users/erh/Library/Developer/Xcode/DerivedData/Motif-fymrucbnitjyghdhgxaviyllghzs/Build/Intermediates/Motif.build/Release-iphonesimulator/Motif-iOS.build/Objects-normal/x86_64/MTFScreenBrightnessThemeApplier.gcda: cannot open: No such file or directory profiling: /Users/erh/Library/Developer/Xcode/DerivedData/Motif-fymrucbnitjyghdhgxaviyllghzs/Build/Intermediates/Motif.build/Release-iphonesimulator/Motif-iOS.build/Objects-normal/x86_64/MTFScreenBrightnessThemeApplier.gcda: cannot open: No such file or directory

    ---- (rest left out in the interest of brevity)

    opened by kunal30xi 2
  • motif-cli

    motif-cli

    Hello,

    I'm really new at iOS development, so forgive me if I'm saying something noobish.

    I've added motif pod to my PodFile and it installed as it should; I need to generate my ThemeSymbols. So, after looking everywhere, I could not find motif-cli to generate it. I'm getting: "-bash: motif: command not found"

    Any thoughts?

    opened by pedrohugorm 2
Releases(0.3.8)
  • 0.3.8(Dec 1, 2017)

  • 0.3.7(Nov 30, 2017)

  • 0.3.6(May 15, 2017)

  • 0.3.5(Jan 12, 2017)

  • 0.3.4(Oct 19, 2016)

  • 0.3.3(Sep 4, 2016)

  • 0.3.2(Mar 25, 2016)

  • 0.3.1(Jan 13, 2016)

  • 0.3.0(Jan 12, 2016)

    Features

    • Migrates to NSErrors to communicate theme application failure in place of runtime exceptions. applyClassWithName:toObject: is now applyClassWithName:to:error: and applier blocks and value transformers are expected to populate a pass-by-reference NSError in the case of failure. (#67)
    • Applying the same theme class twice in a row does not perform the work of applying twice. This is helpful in the context of reusable views where performance is critical. (#70)
    • Adds support for Swift symbol output from the Motif CLI by passing the -s or --swift flag. (#56)
    • Fixes a logic error that could cause the Motif CLI to display an error when there wasn't one when generating theme files. (#57)
    • Adds annotations for Obj-C lightweight generics. (#60)
    • Removes Example project's dependency on Masonry in place of UIStackView. (#61)

    Fixes

    • Mapped constant values are now cached to prevent unnecessary overhead of requesting them on each access. (#75)
    • Codebase cleanup (#62, #63, #64, #65, #68, #69, #71, #72, #73)
    Source code(tar.gz)
    Source code(zip)
    Motif.framework.zip(6.21 MB)
  • 0.2.1(Sep 17, 2015)

  • 0.2.0(Jun 24, 2015)

    Features

    • Adds support for YAML in addition to JSON as a much more human-friendly way of writing your theme files. See the README for some examples (#38)
      • Of course, while YAML is now the recommended way of writing theme files, JSON will still work
    • Adds a class method to NSValueTransformer to enable easy registration of Motif value transformer subclasses without having to declare a new interface & implementation (#28)
    • Removes the need specify value transformer names as part of applier registration (#43)
    • Adds default value transformers for the following: (#42)
      • Creating UIEdgeInsets from an array, dictionary, or number in a theme file
      • Creating CGSize from an array, dictionary, or number in a theme file
      • Creating CGPoint from an array, dictionary, or number in a theme file
      • Creating UIOffset from an array, dictionary, or number in a theme file
    • Removes old string value transformers in favor of the above
    • Adds a keyword applier registration method for easily specifying enums as strings in themes (#31)

    Fixes

    • Fixes a YAML number parsing bug (thanks @jlawton!) (#44)
    • Removes backwards-compatible nullability annotations as Xcode 6.3+ is standard now. (#40)
    • Uses modular imports in headers (#32)
    • Re-throws non-NSUndefinedKeyException exceptions when applying classes (#30)
    • Handles duplicate appliers with different classes (#29)
    • Inlines applier blocks to work around rdar://20723086 (#37)
    • Prevents Carthage from building the tests as part of build 3cc0695
    • Overrides missing designated initializer (#46)
    Source code(tar.gz)
    Source code(zip)
    Motif.framework.zip(1.23 MB)
  • 0.1.2(May 12, 2015)

  • 0.1.1(May 12, 2015)

  • 0.1.0(May 3, 2015)

    Features

    • Add live reloading dynamic theme applier, see the section in the README for documentation

    Fixes

    • Remove unused git submodules from repo
    • Add warning to screen brightness example when run on simulator
    • Ensure that a theme class reference is not applied to a theme class property
    • Fix compilation errors when integrated with an Objective-C++ project
    • Suggest load instead of initialize in Objective-C, due to initialize being called multiple times in categories, which could lead to duplicate appliers being registered
    Source code(tar.gz)
    Source code(zip)
    Motif.framework.zip(579.71 KB)
Owner
Eric Horacek
iOS & design systems @airbnb
Eric Horacek
High performance and lightweight UIView, UIImage, UIImageView, UIlabel, UIButton, Promise and more.

SwiftyUI High performance and lightweight UIView, UIImage, UIImageView, UIlabel, UIButton and more. Features SwiftyView GPU rendering Image and Color

Haoking 336 Nov 26, 2022
A simple, customizable view for efficiently collecting country information in iOS apps.

CountryPickerView CountryPickerView is a simple, customizable view for selecting countries in iOS apps. You can clone/download the repository and run

Kizito Nwose 459 Dec 27, 2022
A customizable color picker for iOS in Swift

IGColorPicker is a fantastic color picker ?? written in Swift. Table of Contents Documentation Colors Style Other features Installation Example Gettin

iGenius 272 Dec 17, 2022
Customizable CheckBox / RadioButton component for iOS

GDCheckbox An easy to use CheckBox/Radio button component for iOS, with Attributes inspector support. Requirements Xcode 10+ Swift 5 iOS 9+ Installati

Saeid 23 Oct 8, 2022
A minimalistic looking banner library for iOS. It supports multiple customizable kinds of Banner types

A minimalistic looking banner library for iOS. It supports multiple customizable kinds of Banner types

Emre Armagan 12 Oct 10, 2022
📊 A customizable gradient progress bar (UIProgressView).

GradientProgressBar A customizable gradient progress bar (UIProgressView). Inspired by iOS 7 Progress Bar from Codepen. Example To run the example pro

Felix M. 490 Dec 16, 2022
Highly customizable Action Sheet Controller with Assets Preview written in Swift

PPAssetsActionController Play with me ▶️ ?? If you want to play with me, just tap here and enjoy! ?? ?? Show me ?? Try me ?? The easiest way to try me

Pavel Pantus 72 Feb 4, 2022
RangeSeedSlider provides a customizable range slider like a UISlider.

RangeSeekSlider Overview RangeSeekSlider provides a customizable range slider like a UISlider. This library is based on TomThorpe/TTRangeSlider (Objec

WorldDownTown 644 Dec 12, 2022
An easy to use UI component to help display a signal bar with an added customizable fill animation

TZSignalStrengthView for iOS Introduction TZSignalStrengthView is an easy to use UI component to help display a signal bar with an added customizable

TrianglZ LLC 22 May 14, 2022
Customizable School Timetable Library

JHTimeTable SwiftUI Customizable School TimeTable Library 설치 Swift Package Manager 사용하기 JHTimeTable뷰를 선언합니다. JHTimeTable(lineColor : .secondary,

LeeProgrammer 4 Jan 6, 2023
A customizable Joystick made with SwiftUI

SwiftUIJoystick ??️ A customizable Joystick made with SwiftUI Create your own Base and Thumb/Handle view using SwiftUI Examples ?? Installation Swift

Michaellis 19 Nov 13, 2022
Fully customizable Facebook reactions like control

Reactions is a fully customizable control to give people more ways to share their reaction in a quick and easy way. Requirements • Usage • Installatio

Yannick Loriot 585 Dec 28, 2022
A beautiful radar view to show nearby items (users, restaurants, ...) with ripple animation, fully customizable

HGRippleRadarView Example To run the example project, clone the repo, and run pod install from the Example directory first. This project is inspired b

Hamza Ghazouani 352 Dec 4, 2022
Easy to use, highly customizable gauge view

GDGauge - Customizable Gauge View Requirements Xcode 11+ Swift 5 iOS 9+ Installation Swift Package Manager .package(url: "https://github.com/saeid/GDG

Saeid 74 Dec 5, 2022
The CITPincode package provides a customizable pincode view

The CITPincode package provides a customizable pincode view. It includes an optional resend code button with a built-in cooldown and an optional divider to be placed anywhere between the cells.

Coffee IT 3 Nov 5, 2022
Lightweight touch visualization library in Swift. A single line of code and visualize your touches!

TouchVisualizer is a lightweight pure Swift implementation for visualising touches on the screen. Features Works with just a single line of code! Supp

Morita Naoki 851 Dec 17, 2022
Lightweight framework for Unsplash in Swift

Lightweight framework for Unsplash in Swift

Pablo Camiletti 12 Dec 30, 2022
📖 A lightweight, paging view solution for SwiftUI

Getting Started | Customization | Installation Getting Started Basic usage Using Pages is as easy as: import Pages struct WelcomeView: View { @S

Nacho Navarro 411 Dec 29, 2022
A super lightweight popView.

SNAugusPopView Features High performance: The library's dependencies all use system libraries and files , only a instance global. Automatic layout: Th

Augus 11 Sep 1, 2022