Reason - Example - Live Reload - Installation - Documentation
layout(
100,
|-email-| ~ 80,
8,
|-password-forgot-| ~ 80,
>=20,
|login| ~ 80,
0
)
email.top(100).left(8).right(8).width(200).height(44)
alignHorizontally(password, forgot)
image.fillContainer()
button.centerInContainer().size(50%)
equalWidths(email, password)
image.width(>=80)
email.Top == 100
password.CenterY == forgot.CenterY
login.Top >= password.Bottom + 20
login.Width == 75 % Width
All Generate native NSLayoutConstraints π
Stevia is part of freshOS iOS toolset. Try it in an example App ! Download Starter Project
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 debugging a nightmare
There must be a better way
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 β€οΈ
- Auto Layout DSL
- Pure Swift
- Simple, this is just NSLayoutConstraint shortcuts, pure UIKit code, no voodoo magic
- Chainable api
- 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
class LoginViewNative:UIView {
let email = UITextField()
let password = UITextField()
let login = UIButton()
convenience init() {
self.init(frame:CGRect.zero)
backgroundColor = .whiteColor()
addSubview(email)
addSubview(password)
addSubview(login)
email.translatesAutoresizingMaskIntoConstraints = false
password.translatesAutoresizingMaskIntoConstraints = false
login.translatesAutoresizingMaskIntoConstraints = false
addConstraint(NSLayoutConstraint(
item: email,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: 8)
)
addConstraint(NSLayoutConstraint(
item: email,
attribute: .Right,
relatedBy: .Equal,
toItem: self,
attribute: .Right,
multiplier: 1,
constant: -8)
)
addConstraint(NSLayoutConstraint(
item: password,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: 8)
)
addConstraint(NSLayoutConstraint(
item: password,
attribute: .Right,
relatedBy: .Equal,
toItem: self,
attribute: .Right,
multiplier: 1,
constant: -8)
)
addConstraint(NSLayoutConstraint(
item: login,
attribute: .Left,
relatedBy: .Equal,
toItem: self,
attribute: .Left,
multiplier: 1,
constant: 0)
)
addConstraint(NSLayoutConstraint(
item: login,
attribute: .Right,
relatedBy: .Equal,
toItem: self,
attribute: .Right,
multiplier: 1,
constant: 0)
)
for b in [email, password, login] {
addConstraint(NSLayoutConstraint(
item: b,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1,
constant: 80)
)
}
addConstraint(NSLayoutConstraint(
item: email,
attribute: .Top,
relatedBy: .Equal,
toItem: self,
attribute: .Top,
multiplier: 1,
constant: 100)
)
addConstraint(NSLayoutConstraint(
item:email,
attribute: .Bottom,
relatedBy: .Equal,
toItem: password,
attribute: .Top,
multiplier: 1,
constant: -8)
)
addConstraint(NSLayoutConstraint(
item: login,
attribute: .Bottom,
relatedBy: .Equal,
toItem: self,
attribute: .Bottom,
multiplier: 1,
constant: 0)
)
email.placeholder = "Email"
email.borderStyle = .RoundedRect
email.autocorrectionType = .No
email.keyboardType = .EmailAddress
email.font = UIFont(name: "HelveticaNeue-Light", size: 26)
email.returnKeyType = .Next
password.placeholder = "Password"
password.borderStyle = .RoundedRect
password.font = UIFont(name: "HelveticaNeue-Light", size: 26)
password.secureTextEntry = true
password.returnKeyType = .Done
login.setTitle("Login", forState: .Normal)
login.backgroundColor = .lightGrayColor()
login.addTarget(self, action: "loginTapped", forControlEvents: .TouchUpInside)
login.setTitle(NSLocalizedString("Login", comment: ""), forState: .Normal)
}
func loginTapped() {
//Do something
}
}
class LoginViewStevia:UIView {
let email = UITextField()
let password = UITextField()
let login = UIButton()
convenience init() {
self.init(frame:CGRect.zero)
backgroundColor = .whiteColor()
sv(
email.placeholder("Email").style(fieldStyle), //.style(emailFieldStyle),
password.placeholder("Password").style(fieldStyle).style(passwordFieldStyle),
login.text("Login").style(buttonStyle).tap(loginTapped)
)
layout(
100,
|-email-| ~ 80,
8,
|-password-| ~ 80,
"",
|login| ~ 80,
0
)
}
func fieldStyle(f:UITextField) {
f.borderStyle = .RoundedRect
f.font = UIFont(name: "HelveticaNeue-Light", size: 26)
f.returnKeyType = .Next
}
func passwordFieldStyle(f:UITextField) {
f.secureTextEntry = true
f.returnKeyType = .Done
}
func buttonStyle(b:UIButton) {
b.backgroundColor = .lightGrayColor()
}
func loginTapped() {
//Do something
}
}
Number of lines From 144 -> 57 ( ~ divided by 2.5)
Number of characters From 4231 -> 1338 ( ~ divided by 3)
Write 3 times less code that is actually 10X more expressive and maintainable <3
You can even enable LiveReload during your development phase! πππ
Stevia + InjectionForXcode = <3 (WhoNeedsReactNative??) π
-
Download InjectionForXcode
-
Install it, Launch it and Go to
File>Install Plugins
(cmd+i) -
Restart Xcode and make sure to click
Load bundles
on the popup
In order to support live reload with InjectionForXcode, we simply need to tell our ViewController to rebuild a view after an injection occured.
in viewDidLoad()
add :
on("INJECTION_BUNDLE_NOTIFICATION") {
self.view = MyView()
}
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:CGRect.zero)
//View code
}
Becomes
convenience init() {
self.init(frame:CGRect.zero)
render()
}
func render() {
//View code
}
And Voila :)
Now you can launch the app and modify whatever you want in the render()
method. simply hit ^= or Product>Inject source
and you'll see your changes Live ! πππ
pod 'SteviaLayout'
use_frameworks!
github "s4cha/Stevia"
-
Create a
Cartfile
file at the root of your project folder -
Add
github "s4cha/Stevia"
to your Cartfile -
Run
carthage update
-
Drag and drop
Stevia.framework
from/Carthage/Build/iOS/
to Linked frameworks and libraries in Xcode (Project>Target>General>Linked frameworks and libraries) -
Add new run script (Project>Target>Build Phases>+> New run script phase)
/usr/local/bin/carthage copy-frameworks
-
Add Input files
$(SRCROOT)/Carthage/Build/iOS/Stevia.framework
There you go!
Copy Stevia source files to your Xcode project
- View Hierarchy
- Layout
- Sizing
- Centering
- Filling
- Aligning
- Following another view
- Horizontal layout
- Vertical layout
- Flexible Margins
- Percentage-Based Layout
- Equations
- Priorities
- Styling
- Content
- Button taps
- Changing constraints
- Simple Changes
- Complex Changes
- Animating Changes
- TableView Cells & CollectionView Cells
- Getting views from the controller
- Complex/Nested layouts
- Known issues
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.
YannickDot, S4cha, Damien, Snowcraft, Mathieu-o
Swift 2 -> version 2.3.0
Swift 3 -> version 3.2.0