TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Mar 2015 |
SPMSupports SPM | ✗ |
Maintained by Robert Payne.
Snap is a light-weight layout framework which wraps AutoLayout with a nicer syntax. Snap has its own layout DSL which provides a chainable way of describing your NSLayoutConstraints which results in layout code that is more concise and readable. Snap supports both iOS and OS X.
Snap uses some Swift-only features like function overloading, so it cannot be used from Objective-C. Because of this we’ve chosen to swap prefixes from Masonry’s
mas_
tosnp_
so you can use both Masonry and Snap in the same project.
Embedded frameworks require a minimum deployment target of iOS 8 or OS X Mavericks.
If you prefer not to use either of the aforementioned dependency managers, you can integrate Snap into your project manually.
cd
-ing into your top-level project directory, and entering the following command:$ git submodule add https://github.com/Masonry/Snap.git
Snap
folder, and drag Snap.xcodeproj
into the file navigator of your app project.Snap.framework
.+
button at the top left of the panel and select "New Copy Files Phase". Rename this new phase to "Copy Frameworks", set the "Destination" to "Frameworks", and add Snap.framework
.Under the hood Auto Layout is a powerful and flexible way of organising and laying out your views. However creating constraints from code is verbose and not very descriptive. Imagine a simple example in which you want to have a view fill its superview but inset by 10 pixels on every side
let superview = self;
let view1 = UIView()
view1.setTranslatesAutoresizingMaskIntoConstraints(false)
view1.backgroundColor = UIColor.greenColor()
superview.addSubview(view1)
let padding = UIEdgeInsetsMake(10, 10, 10, 10)
superview.addConstraints([
NSLayoutConstraint(
item: view1,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: superview,
attribute: NSLayoutAttribute.Top,
multiplier: 1.0,
constant: padding.top
),
NSLayoutConstraint(
item: view1,
attribute: NSLayoutAttribute.Left,
relatedBy: NSLayoutRelation.Equal,
toItem: superview,
attribute: NSLayoutAttribute.Left,
multiplier: 1.0,
constant: padding.left
),
NSLayoutConstraint(
item: view1,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: superview,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1.0,
constant: -padding.bottom
),
NSLayoutConstraint(
item: view1,
attribute: NSLayoutAttribute.Right,
relatedBy: NSLayoutRelation.Equal,
toItem: superview,
attribute: NSLayoutAttribute.Right,
multiplier: 1.0,
constant: -padding.right
)
])
Even with such a simple example the code needed is quite verbose and quickly becomes unreadable when you have more than 2 or 3 views.
Another option is to use Visual Format Language (VFL), which is a bit less long winded.
However the ASCII type syntax has its own pitfalls and its also a bit harder to animate as NSLayoutConstraint.constraintsWithVisualFormat
returns an array.
Heres the same constraints created using ConstraintMaker
let padding = UIEdgeInsetsMake(10, 10, 10, 10)
view1.snp_makeConstraints { make in
make.top.equalTo(superview.snp_top).with.offset(padding.top) // with is an optional semantic filler
make.left.equalTo(superview.snp_left).with.offset(padding.left)
make.bottom.equalTo(superview.snp_bottom).with.offset(-padding.bottom)
make.right.equalTo(superview.snp_right).with.offset(-padding.right)
}
Or even shorter
view1.snp_makeConstraints { make in
make.edges.equalTo(superview).with.insets(padding)
return // this return is a fix for implicit returns in Swift and is only required for single line constraints
}
Also note in the first example we had to add the constraints to the superview superview.addConstraints
.
Snap however will automagically add constraints to the appropriate view.
Snap will also call view1.setTranslatesAutoresizingMaskIntoConstraints(false)
for you.
.equalTo
equivalent to NSLayoutRelation.Equal
.lessThanOrEqualTo
equivalent to NSLayoutRelation.LessThanOrEqual
.greaterThanOrEqualTo
equivalent to NSLayoutRelation.GreaterThanOrEqual
These three equality constraints accept one argument which can be any of the following:
make.centerX.lessThanOrEqualTo(view2.snp_left)
ViewAttribute | NSLayoutAttribute |
---|---|
view.snp_left | NSLayoutAttribute.Left |
view.snp_right | NSLayoutAttribute.Right |
view.snp_top | NSLayoutAttribute.Top |
view.snp_bottom | NSLayoutAttribute.Bottom |
view.snp_leading | NSLayoutAttribute.Leading |
view.snp_trailing | NSLayoutAttribute.Trailing |
view.snp_width | NSLayoutAttribute.Width |
view.snp_height | NSLayoutAttribute.Height |
view.snp_centerX | NSLayoutAttribute.CenterX |
view.snp_centerY | NSLayoutAttribute.CenterY |
view.snp_baseline | NSLayoutAttribute.Baseline |
if you want view.left to be greater than or equal to label.left :
// these two constraints are exactly the same
make.left.greaterThanOrEqualTo(label)
make.left.greaterThanOrEqualTo(label.snp_left)
Auto Layout allows width and height to be set to constant values. if you want to set view to have a minimum and maximum width you could pass a primitive to the equality blocks:
// width >= 200 && width <= 400
make.width.greaterThanOrEqualTo(200)
make.width.lessThanOrEqualTo(400)
However Auto Layout does not allow alignment attributes such as left, right, centerY etc to be set to constant values. So if you pass a primitive for these attributes Snap will turn these into constraints relative to the view’s superview ie:
// creates view.left <= view.superview.left + 10
make.left.lessThanOrEqualTo(10)
You can also use other primitives and structs to build your constraints, like so:
make.top.equalTo(42)
make.height.equalTo(20)
make.size.equalTo(CGSizeMake(50, 100))
make.edges.equalTo(UIEdgeInsetsMake(10, 0, 10, 0))
make.left.equalTo(view).offset(UIEdgeInsetsMake(10, 0, 10, 0))
.prority
allows you to specify an exact priority
.priorityHigh
equivalent to UILayoutPriority.DefaultHigh
.priorityMedium
is half way between high and low
.priorityLow
equivalent to UILayoutPriority.DefaultLow
Priorities are can be tacked on to the end of a constraint chain like so:
make.left.greaterThanOrEqualTo(label.snp_left).with.priorityLow();
make.top.equalTo(label.snp_top).with.priority(600);
Snap also gives you a few convenience methods which create multiple constraints at the same time.
// make top, left, bottom, right equal view2
make.edges.equalTo(view2);
// make top = superview.top + 5, left = superview.left + 10,
// bottom = superview.bottom - 15, right = superview.right - 20
make.edges.equalTo(superview).insets(UIEdgeInsetsMake(5, 10, 15, 20))
// make width and height greater than or equal to titleLabel
make.size.greaterThanOrEqualTo(titleLabel)
// make width = superview.width + 100, height = superview.height - 50
make.size.equalTo(superview).offset(CGSizeMake(100, -50))
// make centerX and centerY = button1
make.center.equalTo(button1)
// make centerX = superview.centerX - 5, centerY = superview.centerY + 10
make.center.equalTo(superview).offset(CGPointMake(-5, 10))
You can chain view attributes for increased readability:
// All edges but the top should equal those of the superview
make.left.right.and.bottom.equalTo(superview)
make.top.equalTo(otherView)
Sometimes you need modify existing constraints in order to animate or remove/replace constraints. In Snap there are a few different approaches to updating constraints.
You can hold on to a reference of a particular constraint by assigning the result of a constraint make expression to a local variable or a class property. You could also reference multiple constraints by storing them away in an array.
var topConstraint: Constraint? = nil
...
// when making constraints
view1.snp_makeConstraints { make in
self.topConstraint = make.top.equalTo(superview).with.offset(padding.top)
make.left.equalTo(superview).with.offset(padding.left)
}
...
// then later you can call
self.topConstraint.uninstall()
snp_remakeConstraints
is similar to snp_makeConstraints
, but will first remove all existing constraints installed by Snap.
func changeButtonPosition() {
self.button.snp_remakeConstraints { make in
make.size.equalTo(self.buttonSize)
if topLeft {
make.top.left.equalTo(10)
} else {
make.bottom.equalTo(self.view).offset(-10)
make.right.equalTo(self.view).offset(-10)
}
}
}
Copy the included code snippets to ~/Library/Developer/Xcode/UserData/CodeSnippets
to write your snap closures at lightning speed!
snp_make
-> <view>.snp_makeConstraints { make in <code> }
snp_remake
-> <view>.snp_remakeConstraints { make in <code> }