layout([
100,
|-emailField-| ~ 80,
8,
|-passwordField-| ~ 80,
"",
|button| ~ 80,
0
])
button.addTarget(self, action: "loginTapped", forControlEvents: .TouchUpInside)
button.tap(loginTapped)
button.setTitle(NSLocalizedString("Login", comment: ""), forState: .Normal)
button.textKey("Login")
NSNotificationCenter.defaultCenter().addObserver(self, selector: "refresh", name: UIApplicationDidBecomeActiveNotification, object: nil)
on(UIApplicationDidBecomeActiveNotification, refresh)
Nadir is an iOS Auto Layout DSL witten in swift.
It is not a heavy layout engine, it is just a lightweight shortcut api for creating Auto Layout constraints.
Wanna layout a login view?
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)
)
layout([
100,
|-emailField-| ~ 80,
8,
|-passwordField-| ~ 80,
"",
|button| ~ 80,
0
])
let views = ["emailField":emailField, "passwordField":passwordField]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-[emailField]-|",
options: NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: views)
)
views = ["emailField":emailField, "passwordField":passwordField]
v("|-[emailField]-|")
|-emailField-|
We personally try to avoid visual format because it's more error prone. Plus the compiler has got you covered with this one :)
addSubview(emailField)
addSubview(passwordField)
addSubview(button)
sv([
emailField,
passwordField,
button
])
emailField.borderStyle = .RoundedRect
emailField.autocorrectionType = .No
emailField.keyboardType = .EmailAddress
emailField.font = UIFont(name: "HelveticaNeue-Light", size: 26)
emailField.returnKeyType = .Next
emailField.style { f in
f.borderStyle = .RoundedRect
f.autocorrectionType = .No
f.keyboardType = .EmailAddress
f.font = UIFont(name: "HelveticaNeue-Light", size: 26)
f.returnKeyType = .Next
}
button.addTarget(self, action: "loginTapped", forControlEvents: .TouchUpInside)
button.tap(loginTapped)
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
}
}
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
}
}
Not a lot less lines (40ish) but the number of total characters is halved!
- The view hierachy is clearer
- Constraints are (WAY) more readable, 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
- Events are a breeze
Less code + More readable == easier to maintain
We know some things can look "easy" but be very complex under the hood. We tried to keep both. We wanted Nadir to be easy to use while remaining very simple. Most of Nadir's code is just simple shortcuts to UIKit Api. Nothing prevents you from mixing Nadir with pure UIKit code. Because Nadir IS pur UIKit code.
At Yummypets we have loooots of views.
After trying diferent methods for building views (Xibs, Storyboard, Splitting Storyboards) React Native even! We found that coding views programatically was the best solution.
Why ? Because CODE DON'T LIE
But coding views programatically had its issues too. That's why we created Nadir.
- No more XML (Thank God!)
- 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 refrencing Storyboards or Xibs by their names "ProfileStoryboard". We all know strings are bad identifiers.
- More consise ex: 1000lines XMl file vs. 30lines code
- Live reload
- Documenting Nadir shortcuts