Go to file
Robert Payne e2112b3ab8 Loosen API a bit to better allow chaining order 2015-04-16 10:07:51 +12:00
CodeSnippets Snap is now SnapKit 2015-04-15 23:07:50 +12:00
SnapKit.xcodeproj Snap is now SnapKit 2015-04-15 23:07:50 +12:00
SnapKit.xcworkspace Snap is now SnapKit 2015-04-15 23:07:50 +12:00
Source Loosen API a bit to better allow chaining order 2015-04-16 10:07:51 +12:00
Tests Snap is now SnapKit 2015-04-15 23:07:50 +12:00
.gitignore Updated gitignore 2015-04-12 22:38:56 +12:00
.travis.yml Snap is now SnapKit 2015-04-15 23:07:50 +12:00
CHANGELOG.md Snap is now SnapKit 2015-04-15 23:07:50 +12:00
LICENSE Snap is now SnapKit 2015-04-15 23:07:50 +12:00
README.md Snap is now SnapKit 2015-04-15 23:07:50 +12:00
SnapKit.podspec Snap is now SnapKit 2015-04-15 23:07:50 +12:00

README.md

SnapKit

====

Build Status

SnapKit is a light-weight layout framework which wraps AutoLayout with a nicer syntax. SnapKit 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. SnapKit supports both iOS and OS X.

SnapKit uses some Swift-only features like function overloading, so it cannot be used from Objective-C. Because of this weve chosen to swap prefixes from Masonrys mas_ to snp_ so you can use both Masonry and SnapKit in the same project.

Requirements

  • iOS 7.0+ / Mac OS X 10.9+
  • Xcode 6.1

Installation

Embedded frameworks require a minimum deployment target of iOS 8 or OS X Mavericks.

CocoaPods

CocoaPods is a dependency manager for Cocoa projects.

CocoaPods 0.36 adds supports for Swift and embedded frameworks. You can install it with the following command:

$ gem install cocoapods

To integrate SnapKit into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!

pod 'SnapKit', '~> 0.10.0'

Then, run the following command:

$ pod install

Carthage

Carthage is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.

You can install Carthage with Homebrew using the following command:

$ brew update
$ brew install carthage

To integrate SnapKit into your Xcode project using Carthage, specify it in your Cartfile:

github "SnapKit/SnapKit" >= 0.10.0

Manually

If you prefer not to use either of the aforementioned dependency managers, you can integrate SnapKit into your project manually.

Embedded Framework

  • Add SnapKit as a submodule by opening the Terminal, cd-ing into your top-level project directory, and entering the following command:
$ git submodule add https://github.com/SnapKit/SnapKit.git
  • Open the SnapKit folder, and drag SnapKit.xcodeproj into the file navigator of your app project.
  • In Xcode, navigate to the target configuration window by clicking on the blue project icon, and selecting the application target under the "Targets" heading in the sidebar.
  • Ensure that the deployment target of SnapKit.framework matches that of the application target.
  • In the tab bar at the top of that window, open the "Build Phases" panel.
  • Expand the "Target Dependencies" group, and add SnapKit.framework.
  • Click on the + 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 SnapKit.framework.

What's wrong with NSLayoutConstraints?

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.

Prepare to meet your Maker!

Heres the same constraints created using ConstraintMaker

let padding = UIEdgeInsetsMake(10, 10, 10, 10)

view1.snp_makeConstraints { (make) -> Void in
  make.top.equalTo(superview.snp_top).offset(padding.top)
  make.left.equalTo(superview.snp_left).offset(padding.left)
  make.bottom.equalTo(superview.snp_bottom).offset(-padding.bottom)
  make.right.equalTo(superview.snp_right).offset(-padding.right)
}

Or even shorter

view1.snp_makeConstraints { (make) -> Void in
  make.edges.equalTo(superview).insets(padding)
}

Also note in the first example we had to add the constraints to the superview superview.addConstraints. SnapKit however will automagically add constraints to the appropriate view.

SnapKit will also call view1.setTranslatesAutoresizingMaskIntoConstraints(false) for you.

Not all things are created equal

.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:

1. ViewAttribute

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

2. UIView/NSView

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)

3. Strict Checks

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 SnapKit will turn these into constraints relative to the views 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))

Learn to prioritize

.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).priorityLow();

make.top.equalTo(label.snp_top).priority(600);

Composition, composition, composition

SnapKit also gives you a few convenience methods which create multiple constraints at the same time.

edges

// 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))

size

// 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))

center

// 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.bottom.equalTo(superview)
make.top.equalTo(otherView)

Hold on for dear life

Sometimes you need modify existing constraints in order to animate or remove/replace constraints. In SnapKit there are a few different approaches to updating constraints.

1. References

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).offset(padding.top).constraint
  make.left.equalTo(superview).offset(padding.left)
}

...
// then later you can call
self.topConstraint.uninstall()

2. snp_remakeConstraints

snp_remakeConstraints is similar to snp_makeConstraints, but will first remove all existing constraints installed by SnapKit.

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)
    }
  }
}

Code Snippets

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> }

TODO

  • Eye candy
  • Example projects
  • Tests