Skip to content

Latest commit

Β 

History

History
269 lines (208 loc) Β· 8.89 KB

README.md

File metadata and controls

269 lines (208 loc) Β· 8.89 KB

Stevia πŸƒ

Language: Swift 2 Platform: iOS 8+ Carthage compatible Build Status Join the chat at https://gitter.im/s4cha/Stevia License: MIT

Elegant view layout for iOS

layout([
    100,
    |-email-| ~ 80,
    8,
    |-password-| ~ 80,
    "",
    |login| ~ 80,
    0
])

Why

Because nothing holds more truth than pure code πŸ€“
Xibs and storyboards are heavy, hard to maintain, hard to merge.
They split the view concept into 2 separate files making making debugging a nightmare
There must be a better way

How

By creating a tool that makes Auto layout code finally readable by a human being.
By coupling it with live code injection such as injectionForXcode we can design views in real time
View layout becomes fun, concise, maintainable and dare I say, beautiful ❀️

What

  • Auto Layout DSL
  • Pure Swift
  • Simple, this is just NSLayoutConstraint shortcuts, pure UIKit code, no voodoo magic
  • Chainable api

Advantages of Stevia πŸƒover Xibs or storyboards

  • No more constraints hell in Interface builder.
  • No more debugging in Interface builder toggling checkboxes.
  • The view code is not split between 2 files anymore
  • What you see is what you get, your view code is in one place, there is no hidden logic elsewere (in the xib)
  • No more referencing Storyboards or Xibs by their names "ProfileStoryboard". We all know strings are bad identifiers.
  • Clear view Hierarchy
  • Live reload, WHAT YOU SEE IS WHAT YOU GET
  • Events are a breeze
  • Code views Faster
  • No more XML (Thank God!)
  • Better readability 1000lines XML file vs. 30lines code
  • Readable constraints (they actually look like the layout itself \o/)
  • Horizontal & vertical layout can be described at the same time
  • Styles are well separated, concise, reusable and can be composed
  • Content like text, placeholders are easier to visualize

Real life example : a classic Login View

alt text

Before

class LoginView:UIView {

    let emailField = UITextField()
    let passwordField = UITextField()
    let button = UIButton()

    convenience init() {
        self.init(frame:CGRectZero)
        backgroundColor = .whiteColor()

        addSubview(emailField)
        addSubview(passwordField)
        addSubview(button)

        addConstraint(NSLayoutConstraint(
          item: emailField,
          attribute: .Left,
          relatedBy: .Equal,
          toItem: self,
          attribute: .Left,
          multiplier: 1,
          constant: 8)
        )
        addConstraint(NSLayoutConstraint(
          item: emailField,
          attribute: .Right,
          relatedBy: .Equal,
          toItem: self,
          attribute: .Right,
          multiplier: 1,
          constant: 8)
        )
        addConstraint(NSLayoutConstraint(item: passwordField, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 8))
        addConstraint(NSLayoutConstraint(item: passwordField, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 8))
        addConstraint(NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: self, attribute: .Left, multiplier: 1, constant: 0))
        addConstraint(NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: self, attribute: .Right, multiplier: 1, constant: 0))
        for b in [emailField, passwordField, button] {
            addConstraint(NSLayoutConstraint(item: b, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 80))
        }
        addConstraint(NSLayoutConstraint(item: emailField, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 100))
        addConstraint(NSLayoutConstraint(item: emailField, attribute: .Bottom, relatedBy: .Equal, toItem: passwordField, attribute: .Top, multiplier: 1, constant: 8))
        addConstraint(NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0))

        emailField.placeholder = "Email"
        emailField.borderStyle = .RoundedRect
        emailField.autocorrectionType = .No
        emailField.keyboardType = .EmailAddress
        emailField.font = UIFont(name: "HelveticaNeue-Light", size: 26)
        emailField.returnKeyType = .Next

        passwordField.placeholder = "Password"
        passwordField.borderStyle = .RoundedRect
        passwordField.font = UIFont(name: "HelveticaNeue-Light", size: 26)
        passwordField.secureTextEntry = true
        passwordField.returnKeyType = .Done

        button.setTitle("Login", forState: .Normal)
        button.backgroundColor = .lightGrayColor()
        button.addTarget(self, action: "loginTapped", forControlEvents: .TouchUpInside)
    }

    func loginTapped() {
    //Do something
    }
}

With Stevia πŸƒ

class LoginView:UIView {

    let emailField = UITextField()
    let passwordField = UITextField()
    let name = UITextField()
    let button = UIButton()

    convenience init() {
        self.init(frame:CGRectZero)
        backgroundColor = .whiteColor()

        sv([
            emailField.placeholder("Email").style(fieldStyle).style(emailFieldStyle),
            passwordField.placeholder("Password").style(fieldStyle).style(passwordFieldStyle),
            button.text("Login").tap(loginTapped).style(buttonSytle)
        ])

        layout([
            100,
            |-emailField-|      ~ 80,
            8,
            |-passwordField-|   ~ 80,
            "",
            |button|            ~ 80,
            0
        ])
    }

    func fieldStyle(f:UITextField) {
        f.borderStyle = .RoundedRect
        f.font = UIFont(name: "HelveticaNeue-Light", size: 26)
        f.returnKeyType = .Next
    }

    func emailFieldStyle(f:UITextField) {
        f.autocorrectionType = .No
        f.keyboardType = .EmailAddress
    }

    func passwordFieldStyle(f:UITextField) {
        f.secureTextEntry = true
        f.returnKeyType = .Done
    }

    func buttonSytle(b:UIButton) {
        b.backgroundColor = .lightGrayColor()
    }

    func loginTapped() {
    //Do something
    }
}

Live Reload

You can even enable LiveReload during your development phase! πŸŽ‰πŸŽ‰πŸŽ‰

Stevia + InjectionForXCode = <3 (WhoNeedsReactNative??) πŸš€

Output sample

In order to support live reload with InjectionForXCode, we simply need to tell our ViewController to rebuild a view after an injection occured.

on("INJECTION_BUNDLE_NOTIFICATION") {
    self.v = CodeRequestView()
    self.view = self.v
}

Currently InjectionForXcode doesn't seem to swizzle "init" methods for some reason. So we have to move our view code in another methods

convenience init() {
    self.init(frame:CGRectZero)
    //View code
}

Becomes

convenience init() {
    self.init(frame:CGRectZero)
    render()
}

func render() {
  //View code
}

And Voila :)

Installation

Manual

Copy Stevia source files to your XCode project

Carthage

github "s4cha/Stevia"

##Rationale behind the project

On the Yummypets app, we needed to deal with looooooots of views.
After trying different methods for building views (Xibs, Storyboards, Splitting Storyboards, React Native even(!).
We found that coding views programmatically was the best solution for us. But coding views programmatically had its issues too : UIKit exposes an imperative verbose API, and it's really easy to create a mess with it. That's why we created Stevia.

Contributors

YannickDot, S4cha, Damien, Snowcraft

Other repos ❀️

Stevia πŸƒ is part of a series of lightweight libraries aiming to make developing iOS Apps a breeze :

  • Async code : then
  • JSON WebServices : ws
  • JSON Parsing : Arrow