nakiostudio/easypeasy
**EasyPeasy** is a Swift framework that lets you create *Auto Layout* constraints
Table of contents
Constants Attributes DimensionAttributes PositionAttributes CompoundAttributes Priorities Conditions ContextualConditions UILayoutGuides Lastly Updating constraints Clearing constraints * Animating constraints
Installation
Swift compatibility
- To work with Swift 2.2 use EasyPeasy
v.1.2.1or earlier versions of the library. - To work with Swift 2.3 use EasyPeasy
v.1.3.1. - To work with Swift 3 use EasyPeasy
v.1.4.2. - To work with Swift 4 use EasyPeasy
v.1.8.0. - To work with Swift 5 use EasyPeasy
v.1.9.0and above.
(thanks Bas van Kuijck).
Cocoapods
EasyPeasy is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "EasyPeasy"Carthage
EasyPeasy is Carthage compatible. To add EasyPeasy as a dependency to your project, just add the following line to your Cartfile:
github "nakiostudio/EasyPeasy"And run carthage update as usual.
Compatibility
EasyPeasy is compatible with iOS (8 and above), tvOS (9 and above) and OS X (10.10 and above). The framework has been tested with Xcode 7 and Swift 2.0, however don't hesitate to report any issues you may find with different versions.
Usage
**EasyPeasy** is a set of position and dimension attributes that you can apply
to your views. You can manage these from the `easy` property available within all
the UI classes that work with Auto Layout (view subclasses, layout guides, etc).
For instance, to set a width of 200px to a view you would create
an attribute of class `Width` with a constant value of `200`, then the attribute
is applied to the view by using the `easy.layout(_:)` method.
```swift
myView.easy.layout(Width(200))
```
Because our view without height is nothing we can apply multiple attributes at
once as follows:
```swift
myView.easy.layout(
Width(200),
Height(120)
)
```
In the previous example, two attributes have been applied and therefore two constraints
created and added: a width constraint with `constant = 200` and a height constraint
with `constant = 120`.
### Constants
Without really knowing it, we have just created an **EasyPeasy** `Constant` struct
containing the constant, multipler and the relation of a `NSLayoutConstraint`.
#### Relations
**EasyPeasy** provides an easy way of creating constants with different
`NSLayoutRelations`:
* `.Equal`: it is created like in our previous example `Width(200)`.
* `.GreaterThanOrEqual`: it is created as easy as this `Width(>=200)` and it means
that our view has a width greater than or equal to 200px.
* `.LessThanOrEqual`: it is created as follows `Width(<=200)`.
#### Multipliers
There is a custom operator that eases the creation of a `NSLayoutConstraint` multiplier.
You can use it like this `Width(*2)` and means that the width of our view is two times
*something*, we will mention later how to establish the relationship with that *something*.
In addition, you can combine `multipliers` with `Equal`, `.GreaterThanOrEqual` and
`LessThanOrEqual` relations. i.e. `Width(>=10.0*0.5)` creates a `NSLayoutConstraint`
with `value = 10.0`, `relation = .GreaterThanOrEqual` and `multiplier = 0.5`, whereas
`Width(==10.0*0.5)` creates a `NSLayoutConstraint` with `value = 10.0`,
`relation = .Equal` and `multiplier = 0.5`.
### Attributes
**EasyPeasy** provides as many `Attribute` classes as attributes `NSLayoutConstraint`
have, plus something that we have called `CompoundAttributes` (we will explain these
attributes later).
#### DimensionAttributes
There are just two dimension attributes `Width` and `Height`. You can create an
*Auto Layout* relationship between your view `DimensionAttribute` and another view
by using the method `func like(view: UIView) -> Self`. Example:
```swift
contentLabel.easy.layout(Width().like(headerView))
```
That line of code will create a constraint that sets a width for `contentLabel`
equal to the `headerView` width.
#### PositionAttributes
The table below shows the different position attributes available. Because they
behave like the `NSLayoutConstraint` attributes, you can find a complete
description of them in the [Apple docs](https://developer.apple.com/library/ios/documentation/AppKit/Reference/NSLayoutConstraint_Class/#//apple_ref/c/tdef/NSLayoutRelation).
Attribute | Attribute | Attribute | Attribute
--- | --- | --- | ---
Left | Right | Top | Bottom
Leading | Trailing | CenterX | CenterY
LeftMargin | RightMargin | TopMargin | BottomMargin
LeadingMargin | TrailingMargin | CenterXWithinMargins | CenterYWithinMargins
FirstBaseline | LastBaseline | -- | --
As well as the **DimensionAttributes** have the `like:` method to establish
*Auto Layout* relationships, you can use a similar method to do the same with
**PositionAttributes**. This method is:
```swift
func to(view: UIView, _ attribute: ReferenceAttribute? = nil) -> Self
```
The example below positions `contentLabel` 10px under `headerView` with the same
left margin as `headerView`.
```swift
contentLabel.easy.layout(
Top(10).to(headerView),
Left().to(headerView, .Left)
)
```
#### CompoundAttributes
These attributes are the ones that create multiple `DimensionAttributes` or
`PositionAttributes` under the hood. For example, the `Size` attribute will create
a `Width` and a `Height` attributes with their width and height
`NSLayoutConstraints` respectively.
These are the `CompoundAttributes` available:
* `Size`: As mentioned before this attribute will apply a `Width` and a `Height`
attribute to the view. It can be initialized in many ways and depending on that
the result may change. These are some examples:
```swift
// Apply width = 0 and height = 0 constraints
view.easy.layout(Size())
// Apply width = referenceView.width and height = referenceView.height constraints
view.easy.layout(Size().like(referenceView))
// Apply width = 100 and height = 100 constraints
view.easy.layout(Size(100))
// Apply width = 200 and height = 100 constraints
view.easy.layout(Size(CGSize(width: 200, height: 100)))
```
* `Edges`: This attribute creates `Left`, `Right`, `Top` and `Bottom` attributes
at once. Examples:
```swift
// Apply left = 0, right = 0, top = 0 and bottom = 0 constraints to its superview
view.easy.layout(Edges())
// Apply left = 10, right = 10, top = 10 and bottom = 10 constraints to its superview
view.easy.layout(Edges(10))
// Apply left = 10, right = 10, top = 5 and bottom = 5 constraints to its superview
view.easy.layout(Edges(UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)))
```
* `Center`: It creates `CenterX` and `CenterY` attributes. Examples:
```swift
// Apply centerX = 0 and centerY = 0 constraints to its superview
view.easy.layout(Center())
// Apply centerX = 10 and centerY = 10 constraints to its superview
view.easy.layout(Center(10))
// Apply centerX = 0 and centerY = 50 constraints to its superview
view.easy.layout(Center(CGPoint(x: 0, y: 50)))
```
* `Margins`: This attribute creates `LeftMargin`, `RightMargin`, `TopMargin` and
`BottomMargin` attributes at once. Examples:
```swift
// Apply leftMargin = 0, rightMargin = 0, topMargin = 0 and bottomMargin = 0 constraints to its superview
view.easy.layout(Margins())
// Apply leftMargin = 10, rightMargin = 10, topMargin = 10 and bottomMargin = 10 constraints to its superview
view.easy.layout(Margins(10))
// Apply leftMargin = 10, rightMargin = 10, topMargin = 5 and bottomMargin = 5 constraints to its superview
view.easy.layout(Margins(UIEdgeInsets(top: 5, left: 10, bottom: 5, right: 10)))
```
* `CenterWithinMargins`: It creates `CenterXWithinMargins` and `CenterYWithinMargins`
attributes. Examples:
```swift
// Apply centerXWithinMargins = 0 and centerYWithinMargins = 0 constraints to its superview
view.easy.layout(CenterWithinMargins())
// Apply centerXWithinMargins = 10 and centerYWithinMargins = 10 constraints to its superview
view.easy.layout(CenterWithinMargins(10))
// Apply centerXWithinMargins = 0 and centerYWithinMargins = 50 constraints to its superview
view.easy.layout(CenterWithinMargins(CGPoint(x: 0, y: 50)))
```
### Priorities
The `Priority` enum does the same function as `UILayoutPriority` and it's shaped
by five cases:
* `low`: it creates an *Auto Layout* priority with `Float` value `1`.
* `medium`: it creates an *Auto Layout* priority with `Float` value `500`.
* `high`: it creates an *Auto Layout* priority with `Float` value `750`.
* `required`: it creates an *Auto Layout* priority with `Float` value `1000`.
* `custom`: it specifies the *Auto Layout* priority defined by the
developer in the case associated value `value`. Example: `.custom(value: 650.0)`.
In order to apply any of these priorities to an `Attribute`, the method
`.with(priority: Priority)` must be used. The following example gives an
`UILayoutPriority` of `500` to the `Top` `Attribute` applied to `view`:
```swift
view.easy.layout(Top(>=50).with(.medium))
```
You can also apply a `Priority` to an array of `Attributes` (this operation will
override the priorities previously applied to an `Attribute`).
```swift
view.easy.layout([
Width(200),
Height(200)
].with(.medium))
```
### Conditions
One of the peculiarities of **EasyPeasy** is the usage of `Conditions` or closures
that evaluate whether a constraint should be applied or not to the view.
The method `when(condition: Condition)` sets the `Condition` closure to an `Attribute`.
There is plenty of use cases, the example below shows how to apply different
constraints depending on a custom variable:
```swift
var isCenterAligned = true
...
view.easy.layout(
Top(10),
Bottom(10),
Width(250),
Left(10).when { !isCenterAligned },
CenterX(0).when { isCenterAligned }
)
```
#### Condition re-evaluation
These `Condition` closures can be re-evaluated during the lifecycle of a view,
to do so you just need to call the convenience method `easy.reload()`.
```swift
view.easy.reload()
```
Bare in mind that these `Condition` closures are stored in properties therefore
you need to capture those variables you access within the closure. For example:
```swift
descriptionLabel.easy.layout(
Height(100).when { [weak self] in
return self?.expandDescriptionLabel ?? false
}
)
```
You can also apply a `Condition` to an array of `Attributes` (this operation will
override the `Conditions` previously applied to an `Attribute`).
```swift
view.easy.layout([
Width(200),
Height(240)
].when { isFirstItem })
view.easy.layout([
Width(120),
Height(140)
].when { !isFirstItem })
```
#### ContextualConditions
This iOS only feature is a variant of the `Condition` closures that receive no
parameters and return a boolean value. Instead, a `Context` struct is passed
as parameter providing some extra information based on the `UITraitCollection`
of the `UIView` the `Attributes` are going to be applied to.
The properties available on this `Context` struct are:
* `isPad`: true if the current device is iPad.
* `isPhone`: true if the current device is iPhone.
* `isHorizontalVerticalCompact`: true if both horizontal and vertical size
classes are `.Compact`.
* `isHorizontalCompact`: true if the horizontal size class is `.Compact`.
* `isVerticalCompact`: true if the vertical size class is `.Compact`.
* `isHorizontalVerticalRegular`: true if both horizontal and vertical size
classes are `.Regular`.
* `isHorizontalRegular`: true if the horizontal size class is `.Regular`.
* `isVerticalRegular`: true if the vertical size class is `.Regular`.
This is an example of `ContextualConditions` applied to an array of
`Attributes`:
```swift
view.easy.layout([
Size(250),
Center(0)
].when { $0.isHorizontalRegular })
view.easy.layout([
Top(0),
Left(0),
Right(0),
Height(250)
].when { $0.isHorizontalCompact })
```
##### ContextualCondition re-evaluation
As we have seen before, you can re-evaluate a `Condition` closure by calling
the `easy.reload()` convenience method. This also applies to
`ContextualConditions`, therefore if you want your constraints to be updated
upon a change on your view `UITraitCollection` then you need to call the
`easy.reload()` method within `traitCollectionDidChange(_:)`.
Alternatively, **EasyPeasy** can do this step for you automatically. This is
disabled by default as it requires method swizzling; to enable it simply
**compile the framework** adding the compiler flags `-D EASY_RELOAD`.
### UILayoutGuides
Since the version *v.0.2.3* (and for iOS 9 projects and above) **EasyPeasy**
integrates `UILayoutGuides` support.
#### Applying constraints
Applying a constraint to an `UILayoutGuide` is as easy as we have discussed in the
previous sections, just apply the **EasyPeasy** attributes you want using the
`easy.layout(_:)` method.
```swift
func viewDidLoad() {
super.viewDidLoad()
let layoutGuide = UILayoutGuide()
self.view.addLayoutGuide(layoutGuide)
layoutGuide.easy.layout(
Top(10),
Left(10),
Right(10),
Height(100).when { Device() == .iPad },
Height(60).when { Device() == .iPhone }
)
}
```
As you can see, all the different attributes and goodies **EasyPeasy** provides for
`UIViews` are also applicable to `UILayoutGuides`.
#### Connecting UILayoutGuides and UIViews
As mentioned in the [Attributes](#attributes) section you can create constraint
relationships between an `UIView` attribute and other `UIViews` attributes using
the methods `to(_:_)` and `like(_:_)`. Now you can take advantage of those methods
to create a relationship between your `UIView` attributes and an `UILayoutGuide`.
```swift
let layoutGuide = UILayoutGuide()
let separatorView: UIView
let label: UILabel
func setupLabel() {
self.label.easy.layout(
Top(10).to(self.layoutGuide),
CenterX(0),
Size(60)
)
self.separatorView.easy.layout(
Width(0).like(self.layoutGuide),
Height(2),
Top(10).to(self.label),
CenterX(0).to(self.label)
)
}
```
### Lastly
Finally but not less important in this section we will explain how to interact
with `Attributes` once they have been applied to an `UIView` using the
`easy.layout(_:)` method.
#### Updating constraints
We briefly mentioned in the introductory section that **EasyPeasy** solves most
of the constraint conflicts and it's true. Usually, in order to update a constraint
or the constant of a constraint you have to keep a reference to your
`NSLayoutConstraint` and update the constant when needed. With **EasyPeasy** you
just need to apply another `Attribute` to your `UIView` of the same or different
type. In the example below we have two methods, the one in which we setup our
constraints `viewDidLoad()` and a method in which we want to update the `Top`
attribute of our `headerView`.
```swift
func viewDidLoad() {
super.viewDidLoad()
headerView.easy.layout(
Top(0),
Left(0),
Right(0),
Height(60)
)
}
func didTapButton(sender: UIButton?) {
headerView.easy.layout(Top(100))
}
```
That's it! we have updated our `Top` constraint without caring about keeping
references or installing/uninstalling new constraints.
However, there is some cases in which **EasyPeasy** cannot prevent a conflict (at
least for now). This is when multiple constraints cannot be satisfied, i.e. existing
a `Left` and `Right` constraints it's also applied a `Width` constraint (all of them
with the same priority). But **EasyPeasy** is smart enough to prevent conflicts,
i.e. when replacing a `Left` and `Right` attributes with a `CenterX` attribute.
#### Clearing constraints
**EasyPeasy** provides a method extending `UIView` that clears all the constraints
installed in an `UIView` by the framework. This method is `func easy.clear()`.
```swift
view.easy.clear()
```
#### Animating constraints
Animating constraints with **EasyPeasy** is very straightforward, just apply one
or more `Attributes` to your view within an animation block and you are ready to
go, without worrying about constraint conflicts. Example:
```swift
UIView.animateWithDuration(0.3) {
view.easy.layout(Top(10))
view.layoutIfNeeded()
}
```Example projects
Don't forget to clone the repository and run the iOS and OS X example projects to see EasyPeasy in action.
<h3 align="center"> <table width="50%"> <tr> <td width="50%"> <img src="/README/demo_ios.gif" alt="Demo iOS" /> </td> <td width="50%"> <img src="/README/demo_macos.gif" alt="Demo macOS" /> </td> </tr> </table> </h3>
Note: the messages in the demo app aren't real and the appearance of those Twitter accounts no more than a tribute to some kickass developers :)
EasyPeasy playground
Alternatively, you can play with EasyPeasy cloning the Playground project available here.
<h3 align="center"> <img src="/README/playground.gif" alt="Playground" /> </h3>
Autogenerated documentation
EasyPeasy is a well documented framework and therefore all the documented classes and methods are available in Cocoadocs.
License
EasyPeasy is available under the MIT license. See the LICENSE file for more info.
Package Metadata
Repository: nakiostudio/easypeasy
Default branch: master
README: README.md