Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/nakiostudio/EasyPeasy

Auto Layout made easy
https://github.com/nakiostudio/EasyPeasy

auto auto-layout constraints easypeasy ios layout swift

Last synced: 3 months ago
JSON representation

Auto Layout made easy

Awesome Lists containing this project

README

        


EasyPeasy Logo

[![CI Status](http://img.shields.io/travis/nakiostudio/EasyPeasy.svg?style=flat)](https://travis-ci.org/nakiostudio/EasyPeasy)
[![Version](https://img.shields.io/cocoapods/v/EasyPeasy.svg?style=flat)](http://cocoapods.org/pods/EasyPeasy)
[![Carthage compatible](https://img.shields.io/badge/carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://travis-ci.org/nakiostudio/EasyPeasy)

**EasyPeasy** is a Swift framework that lets you create *Auto Layout* constraints
programmatically without headaches and never ending boilerplate code. Besides the
basics, **EasyPeasy** resolves most of the constraint conflicts for you and also
can attach to a constraint conditional closures that are evaluated before applying
a constraint, this way you can install an *Auto Layout* constraint depending on
platform, size classes, orientation... or the state of your controller, easy peasy!

In this quick tour through **EasyPeasy** we assume that you already know the
advantages and disadvantages of the different *Auto Layout* APIs and therefore you
won't see here a comparison of the code side by side, just read and decide
whether **EasyPeasy** is for you or not.

### A touch of EasyPeasy
The example below is quite simple but shows how effortless its implementation
result using **EasyPeasy**.




First touch

### Features

* Compatible with iOS, tvOS and OS X.
* Lightweight and easy to use domain specific language.
* Resolution of *Auto Layout* conflicts.
* Fast and *hassle-free* update of constraints.
* Conditional application of constraints.
* `UILayoutGuide` and `NSLayoutGuide` support.

### Guides

* [Mastering Auto Layout with EasyPeasy I: Introduction](https://medium.com/@yaid/mastering-auto-layout-with-easypeasy-i-introduction-341f12230074)
* [Mastering Auto Layout with EasyPeasy II: Basics](https://medium.com/@yaid/mastering-auto-layout-with-easypeasy-ii-basics-3057e1db817)
* [Mastering Auto Layout with EasyPeasy III: Relationships](https://medium.com/@yaid/mastering-auto-layout-with-easypeasy-iii-relationships-91fc140e4755)
* Mastering Auto Layout with EasyPeasy IV: Priorities *(coming soon...)*
* Mastering Auto Layout with EasyPeasy V: Layout Guides *(coming soon...)*
* Mastering Auto Layout with EasyPeasy VI: Advanced *(coming soon...)*

## Table of contents

* [Installation](#installation)
* [Usage](#usage)
* [Constants](#constants)
* [Attributes](#attributes)
* [DimensionAttributes](#dimensionattributes)
* [PositionAttributes](#positionattributes)
* [CompoundAttributes](#compoundattributes)
* [Priorities](#priorities)
* [Conditions](#conditions)
* [ContextualConditions](#contextualconditions)
* [UILayoutGuides](#uilayoutguides)
* [Lastly](#lastly)
* [Updating constraints](#updating-constraints)
* [Clearing constraints](#clearing-constraints)
* [Animating constraints](#animating-constraints)
* [Example projects](#example-projects)
* [EasyPeasy playground](#easypeasy-playground)
* [Autogenerated documentation](#autogenerated-documentation)

## Installation

### Swift compatibility
* To work with Swift 2.2 use EasyPeasy `v.1.2.1` or 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.0` and above.
(thanks [Bas van Kuijck](https://github.com/basvankuijck)).

### Cocoapods
EasyPeasy is available through [CocoaPods](http://cocoapods.org). To install
it, simply add the following line to your Podfile:

```ruby
pod "EasyPeasy"
```

### Carthage
EasyPeasy is [Carthage](https://github.com/Carthage/Carthage) compatible.
To add **EasyPeasy** as a dependency to your project, just add the following line
to your Cartfile:

```ruby
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.





Demo iOS


Demo macOS

*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](https://github.com/nakiostudio/EasyPeasy-Playground).


Playground

## Autogenerated documentation

**EasyPeasy** is a well documented framework and therefore all the documented
classes and methods are available in [Cocoadocs](http://cocoadocs.org/docsets/EasyPeasy).

## Author

Carlos Vidal - [@nakiostudio](https://twitter.com/nakiostudio)

## License

EasyPeasy is available under the MIT license. See the LICENSE file for more info.