Skip to content

Your friendly NSLayoutConstraint Aid

License

Notifications You must be signed in to change notification settings

drewdeponte/Constraid

Repository files navigation

Crazy Man in Straight Jacket

Join the chat at https://gitter.im/uptech/Constraid

Constraid

Constraid is your personal NSLayoutConsraint Aid. It's job is to make it as simple as possible for you to programmatically use AutoLayout in your iOS & macOS projects.

For example if you have a child view (childView) added as a subview of a parent view (parentView) and you want that child view to match the width of the parent view and be flush with the top edge of the parent view you would normally do the following with AutoLayout:

childView.translatesAutoresizingMaskIntoConstraints = false
childView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
childView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
childView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true

However, with the aid of Constraid it is as simple as

cup(childView, byTopEdgeOf: parentView).activate()

The power of Constraid is that it provides higher level concepts such as cup, flush, etc. that make it easier to reason about your layouts, and does so in a way fully discoverable via auto-completion and focused on reducing the amount of code necessary to properly define layouts.

Another powerful concept it introduces is constraint collections which can be combined as follows:

let constraints = flush(childView, withVerticalEdgesOf: parentView) +
                  center(childView, verticallyWithin: parentView)
constraints.activate()

Since it is common to apply multiple constraints to a view. Constraid uses a proxy pattern allowing you to implement the above in a much more concise syntax.

let proxy = childView.constraid
              .flush(withVerticalEdgesOf: parentView)
              .center(verticallyWithin: parentView)
proxy.activate()

The above is extremely useful when doing AutoLayout Animation or when you simply want to deactivate or activate a collection of constraints.

However, if you don't need to reference the proxy to the collection of constraints for later to use for animation, etc. you can simply activate at the end of the chain.

childView.constraid
  .flush(withVerticalEdgesOf: parentView)
  .center(verticallyWithin: parentView)
  .activate()

Crazy Woman in Straight Jacket

Setup

Swift Package Manager is the best way to add Constraid to your project.

However, we also support Carthage as well. To use Carthage simply do the following:

  1. Add the following to your Cartfile.

    github "uptech/Constraid"
    
  2. Follow the Carthage instructions for adding frameworks to your application.

Usage

Constraid tries to simplify things by building on top of the lower level constructs provided by Apple's AutoLayout system.

In general Constraid provides a number of methods that aid with defining constraint relationships between views. Currently, the provided methods are grouped into the following sections.

Flush

Lets say you want viewA to sit flush or right along viewB's edges, meaning be sized the same size as viewB. You can accomplish this by using the following:

flush(viewA, withEdgesOf: viewB)

If on the other hand you wanted viewA to fill the space of viewB up to viewB's margins, you could simply do.

flush(viewA, withMarginsOf: viewB)

The flush methods simply create one or more equivalency constraints between the two views and the appropriate attributes. A list of the short versions of all the flush methods are provided below.

flush(viewA, withLeadingEdgeOf: viewB)
flush(viewA, withTrailingEdgeOf: viewB)
flush(viewA, withTopEdgeOf: viewB)
flush(viewA, withBottomEdgeOf: viewB)

flush(viewA, withVerticalEdgesOf: viewB)
flush(viewA, withHorizontalEdgesOf: viewB)

flush(viewA, withEdgesOf: viewB)

flush(viewA, withLeadingMarginOf: viewB)
flush(viewA, withTrailingMarginOf: viewB)
flush(viewA, withTopMarginOf: viewB)
flush(viewA, withBottomMarginOf: viewB)

flush(viewA, withVerticalMarginsOf: viewB)
flush(viewA, withHorizontalMarginsOf: viewB)

flush(viewA, withMarginsOf: viewB)

First Aid Case

Center

Ok so you want viewA to be centered inside of viewB's edges. You can accomplish this by using the following:

center(viewA, within: viewB)

If on the other hand you wanted viewA to be centred inside of viewBs margins you could simply do.

center(viewA, withinMarginsOf: viewB)

The center methods simply create one or more equivalency constraints between the two views and the appropriate attributes (centerX, centerY, centerXwithinMargins, centerYwithinMargins). A list of the short versions of all the center methods are provided below.

center(viewA, verticallyWithin: viewB)
center(viewA, horizontallyWithin: viewB)

center(viewA, within: viewB)

center(viewA, verticallyWithinMarginsOf: viewB)
center(viewA, horizontallyWithinMarginsOf: viewB)

center(viewA, withinMarginsOf: viewB)

Cup

Lets say you want viewA to sit flush or right along viewB's leading, top, and trailing edges. You can accomplish this by using the following:

cup(viewA, byTopEdgeOf: viewB)

If on the other hand you wanted viewA to sit flush or right along viewB up to viewB's leading, top, and trailing margins, you could simply do.

cup(viewA, byTopMarginOf: viewB)

The cup methods simply create three equivalency constraints between the two views and the appropriate attributes to cup the viewA along 3 edges. A list of the short versions of all the cup methods are provided below.

cup(viewA, byTopEdgeOf: viewB)
cup(viewA, byBottomEdgeOf: viewB)
cup(viewA, byLeadingEdgeOf: viewB)
cup(viewA, byTrailingEdgeOf: viewB)

cup(viewA, byTopMarginOf: viewB)
cup(viewA, byBottomMarginOf: viewB)
cup(viewA, byLeadingMarginOf: viewB)
cup(viewA, byTrailingMarginOf: viewB)

Manage Size

Lets say you want viewA's width to be explicitly sized to some 100 pts. You can accomplish this by using the following:

setWidth(of: viewA, to: 100)

Bandaid

If on the other hand you wanted viewA's height to be explicitly sized to 100 pts you would do.

setHeight(of: viewA, to: 100)

The setWidth and setHeght methods simply create one or more equivalency constraints between the view and the appropriate attributes. A list of the short versions of all the setWidth and setHeight methods are provided below.

setWidth(of: item, to: constant)
setHeight(of: item, to: constant)

If you want to expand from a minimum width you can do so using the following:

expandWidth(of: item, from: constant)
expandHeight(of: item, from: constant)
limitWidth(of: item, by: constant)
limitHeight(of: item, by: constant)

Similarly, if you want to make the width of one view equal to another view's width you can do this:

matchWidth(of: viewA, to: viewB)

And for the height:

matchHeight(of: viewA, to: viewB)

When you want to make a view's height and width equal you can do:

equalize(viewA)

When you want to make a view have a specific aspect ratio you can do:

let size = CGSize(width: 3, height: 5)
setAspectRatio(of: viewA, toSize: size)

Manage Relative Position

Lets say you want viewA to be position right after viewB's edge. You can accomplish this by using the following:

follow(theTrailingEdgeOf: viewB, with: viewA)

Or lets say you want viewA to be positioned in front of viewB's edge. You could do the following.

precede(theLeadingEdgeOf: viewB, with: viewA)

If on the other hand you want to control the vertical position you can use set.

These methods simply create one or more equivalency constraints between the two views and the appropriate attributes. A list of the short versions of all these methods are provided below.

follow(theTrailingEdgeOf:with:)
follow(theTrailingMarginOf:with:)
follow(theCentorOf:with:)

precede(theLeadingEdgeOf:with:)
precede(theLeadingMarginOf:with:)
precede(theCenterOf:with:)

set(viewA, aboveTheTopEdgeOf:)
set(viewA, aboveTheTopMarginOf:)
set(viewA, aboveTheCenterOf:)
set(viewA, belowTheBottomEdgeOf:)
set(viewA, belowTheBottomMarginOf:)
set(viewA, belowTheCenterOf:)

Not Infinity

Limit

Lets say you want viewA to be limited by viewB's edges. You can accomplish this by using the following:

limit(viewA, byEdgesOf: viewB)

This sets up constraints saying that each of viewA's edges must be <= viewB's respective edges.

If on the other hand you wanted viewA limited by viewBs margins you could simply do.

limit(viewA, byMarginsOf: viewB)

The limit methods simply create one or more <= constraints between the two views and the appropriate attributes. A list of the short versions of all the limit methods are provided below.

limit(viewA, byLeadingEdgeOf: viewB)
limit(viewA, byTrailingEdgeOf: viewB)
limit(viewA, byTopEdgeOf: viewB)
limit(viewA, byBottomEdgeOf: viewB)

limit(viewA, byVerticalEdgesOf: viewB)
limit(viewA, byHorizontalEdgesOf: viewB)

limit(viewA, byEdgesOf: viewB)

limit(viewA, byLeadingMarginOf: viewB)
limit(viewA, byTrailingMarginOf: viewB)
limit(viewA, byTopMarginOf: viewB)
limit(viewA, byBottomMarginOf: viewB)

limit(viewA, byVerticalMarginsOf: viewB)
limit(viewA, byHorizontalMarginsOf: viewB)

limit(viewA, byMarginsOf: viewB)

Expand

Lets say you want viewA to be expanded from viewB's edges out. You can accomplish this by using the following:

expand(viewA, fromEdgesOf: viewB)

This sets up constraints saying that each of viewA's edges must be >= viewB's respective edges.

If on the other hand you wanted viewA to be expanded from viewBs margins you could simply do.

expand(viewA, fromMarginsOf: viewB)

The expand methods simply create one or more >= constraints between the two views and the appropriate attributes. A list of the short versions of all the expand methods are provided below.

expand(viewA, fromLeadingEdgeOf: viewB)
expand(viewA, fromTrailingEdgeOf: viewB)
expand(viewA, fromTopEdgeOf: viewB)
expand(viewA, fromBottomEdgeOf: viewB)

expand(viewA, fromVerticalEdgesOf: viewB)
expand(viewA, fromHorizontalEdgesOf: viewB)

expand(viewA, fromEdgesOf: viewB)

expand(viewA, fromLeadingMarginOf: viewB)
expand(viewA, fromTrailingMarginOf: viewB)
expand(viewA, fromTopMarginOf: viewB)
expand(viewA, fromBottomMarginOf: viewB)

expand(viewA, fromVerticalMarginsOf: viewB)
expand(viewA, fromHorizontalMarginsOf: viewB)

expand(viewA, fromMarginsOf: viewB)

expand(viewA, fromWidthOf: viewB)
expand(viewA, fromHeightOf: viewB)

Manage Intrinsic Size Relations

These allow you to define the content hugging and compression resistance priorities and constraints for a given view. Note: The priorities are linked so that they will be the same. If you want to set them independently you should use the native API provided by Apple for this.

keepIntrinsicHeight(of: viewA, priority:)
keepIntrinsicWidth(of: viewA, priority:)

keepIntrinsicSize(of: viewA, priority:)

Build

We supports Carthage and therefore this project can be built using the following.

carthage build --no-skip-current

However, if you don't want to use Carthage or you are having difficulties with build failures you can try the following which should provide some more details.

xcodebuild -scheme Constraid-MacOS -project Constraid.xcodeproj build
xcodebuild -scheme Constraid-iOS -project Constraid.xcodeproj build

License

Constraid is Copyright © 2017-2019 UpTech Works, LLC. It is free software, and may be redistributed under the terms specified in the LICENSE file.

Our Mascots

The mascots for our project are the work of Ron Leishman. We licensed them and they are available for licensing if you like from shutterstock.

About

Constraid is maintained and funded by Uptech Studio, a software product, design & development consultancy.

We love open source software. See our other projects or hire us to design, develop, and grow your product.