Home

Awesome

<p align="center"> <a href="https://github.com/layoutBox/PinLayout"><img src="docs/pinlayout-logo-text.png" width="200" /></a> </p> <p align="center"> <a href="https://github.com/layoutBox/PinLayout"><img src="https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS%20%7C%20macOS-red.svg"/></a> <a href="https://github.com/layoutBox/PinLayout"><img src="https://img.shields.io/badge/languages-Swift%20%7C%20ObjC-red.svg"/></a> </p> <p align="center"> <a href='https://cocoapods.org/pods/PinLayout'><img src="https://img.shields.io/cocoapods/v/PinLayout.svg?style=flat" /> <a href="https://github.com/Carthage/Carthage"><img src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" /></a> <a href="https://swift.org/package-manager/"><img src="https://camo.githubusercontent.com/57aa80b42087a088cdf607fb98c0224bccf1b441/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f73706d2d636f6d70617469626c652d627269676874677265656e2e7376673f7374796c653d666c6174" /></a> <a href="https://github.com/layoutBox/PinLayout/actions/workflows/github-actions-ci.yml"><img src="https://github.com/layoutBox/PinLayout/actions/workflows/github-actions-ci.yml/badge.svg?branch=master"/></a> <!-- <a href="https://codecov.io/gh/layoutBox/PinLayout"><img src="https://codecov.io/gh/layoutBox/PinLayout/branch/master/graph/badge.svg"/></a> --> <a href="https://raw.githubusercontent.com/layoutBox/PinLayout/master/LICENSE"><img src="https://img.shields.io/cocoapods/l/PinLayout.svg" /></a> </p>

Extremely Fast views layouting without auto layout. No magic, pure code, full control and blazing fast. Concise syntax, intuitive, readable & chainable. PinLayout can layouts UIView, NSView and CALayer.

"No Auto layout constraints attached"

Requirements

Recent changes/features

Content

<br>

:pushpin: PinLayout is actively updated. So please come often to see latest changes. You can also Star it to be able to retrieve it easily later.

PinLayout and layoutBox

<a href="https://github.com/layoutBox/PinLayout"><img src="docs/images/pinlayout_plus_layoutBox.png" width="200"/></a>

PinLayout is part of the layoutBox organization containing few Open Source projects related to layout using Swift. See layoutBox.

PinLayout + Autolayout

You don't need to choose, you can layout some views using PinLayout and some other with autolayout. Your views just to need to implement the autolayout intrinsicContentSize properties.

<br>

<a name="intro_usage_examples"></a>

Introduction examples

Example 1:

This example layout an image, a UISegmentedControl, a label and a line separator. This example adjusts its content to match the device's size and orientation changes.

<a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/Intro/IntroView.swift"><img src="docs/images/pinlayout_intro_example_iphonex.png"/></a>

override func layoutSubviews() {
   super.layoutSubviews() 
   let padding: CGFloat = 10
    
   logo.pin.top(pin.safeArea).left(pin.safeArea).width(100).aspectRatio().margin(padding)
   segmented.pin.after(of: logo, aligned: .top).right(pin.safeArea).marginHorizontal(padding)
   textLabel.pin.below(of: segmented, aligned: .left).width(of: segmented).pinEdges().marginTop(10).sizeToFit(.width)
   separatorView.pin.below(of: [logo, textLabel], aligned: .left).right(to: segmented.edge.right).marginTop(10)
}
<br/>
Example 2:

This example shows how easily PinLayout can adjust its layout based on the view's container size.

<a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/AdjustToContainer/Subviews/ChoiceSelectorView.swift"><img src="docs/pinlayout_example_adjust_to_container2.png" width="640"/></a>

  let margin: CGFloat = 12
        
  if frame.width < 500 {
      textLabel.pin.top().horizontally().margin(margin).sizeToFit(.width)
      segmentedControl.pin.below(of: textLabel).right().margin(margin)
  } else {
      segmentedControl.pin.top().right().margin(margin)
      textLabel.pin.top().left().before(of: segmentedControl).margin(margin).sizeToFit(.width)
  }

:pushpin: This example is available in the Examples App. See example complete source code

<a name="introduction"></a>

PinLayout principles and philosophy

<a name="performance"></a>

PinLayout's Performance

PinLayout's performance has been measured using the Layout Framework Benchmark.

As you can see in the following chart, PinLayout are faster or equal to manual layouting, and between 8x and 12x faster than auto layout, and this for all types of iPhone (5S/6/6S/7/8/X)

See here for more details, results and explanation of the benchmark.

<p align="center"> <a href="docs/Benchmark.md"> <img src="docs/Benchmark/benchmark_comparison_all_small.png" width=660/> </a> </p>

<a name="documentation"></a>

Documentation

UIKit safeAreaInsets support

PinLayout can easily handle iOS 11 UIView.safeAreaInsets, but it goes even further by supporting safeAreaInsets for previous iOS releases (including iOS 7/8/9/10) by adding a property UIView.pin.safeArea. See here for more details

macOS support

PinLayout support macOS 10.9+.

:pushpin: In this documentation, any methods with parameters of type UIView or UIEdgeInsets are also supported on macOS, using NSView and NSEdgeInsets. See macOS Support for more information.

<a name="rtl_support"></a>

Right to left languages (RTL) support

PinLayout supports left-to-right (LTR) and right-to-left (RTL) languages.

See here for more details.

<br/>

<a name="layout_edges"></a>

Edges layout

PinLayout can position a view’s edge relative to its superview edges.

Example:

This example layout the view A to fit its superview frame with a margin of 10 pixels. It pins the top, left, bottom and right edges.

<img src="docs/01-example-distance-superview-edge.png" width="620"/>
    viewA.pin.top(10).bottom(10).left(10).right(10)

Another shorter possible solution using all():

    view.pin.all(10)

Methods:

The following methods are used to position a view’s edge relative to its superview edges.

:pushpin: The offset/margin parameter in the following methods can be either positive and negative. In general cases positive values are used.

Methods supporting LTR (left-to-right) and RTL (right-to-left) languages.

<a name="pin_multiple_edges"></a> Methods pinning multiple edges:

Usage Examples:
   view.pin.top(20).bottom(20)   // The view has a top margin and a bottom margin of 20 pixels 
   view.pin.top().left()         // The view is pinned directly on its parent top and left edge
   view.pin.all()                // The view fill completely its parent (horizontally and vertically)
   view.pin.all(pin.safeArea)    // The view fill completely its parent safeArea 
   view.pin.top(25%).hCenter()   // The view is centered horizontally with a top margin of 25%
   view.pin.left(12).vCenter()   // The view is centered vertically
   view.pin.start(20).end(20)    // Support right-to-left languages.
   view.pin.horizontally(20)     // The view is filling its parent width with a left and right margin.
   view.pin.top().horizontally() // The view is pinned at the top edge of its parent and fill it horizontally.
<br/>

Layout multiple edges relative to superview

This section describe methods that are similar to methods describe in the previous section Edges layout, except that they position 2 edges simultaneously. They can be used as shortcuts to set 2 consecutive edges.

<img src="docs/pinlayout-anchors.png" width="400"/>
Example:

This example position the view’s on the top-right corner of its superview’s topRight and set its size to 100 pixels.

<img src="docs/images/pinlayout_example_topRight.png" width="600"/>
	viewA.pin.topRight().size(100)

This is equivalent to:

	viewA.pin.top().right().size(100)

Methods:

:pushpin: The offset parameter in the following methods can be either positive and negative. In general cases positive values are used.

Methods supporting LTR (left-to-right) and RTL (right-to-left) languages.
Usage Examples:
   // Position a view at the top left corner with a top and left margin of 10 pixels
   view.pin.topLeft(10)

   // Position the 4 edges with a margin of 10 pixels.
   view.pin.topLeft(10).bottomRight(10)
<br/>

<a name="relative_edges_layout"></a>

Relative Edges layout

Layout using relative positioning

PinLayout has methods to position relative to other views. The view can be layouted relative to one or many relative views. The following methods layout one view's edge (top, bottom, left or right).

Methods:

:pushpin: Multiple relative views: If for example a call to `below(of: [...]) specify multiple relative views, the view will be layouted below ALL these views.

:pushpin: These methods can pin a view’s relative to any views, even if they don't have the same direct superview! It works with any views that have a shared ancestor.

Usage examples:
	view.pin.after(of: view4).before(of: view1).below(of: view3)
	view.pin.after(of: view2)
	view.pin.below(of: [view2, view3, view4])
Example:

The following example will position the view C between the view A and B with margins of 10px using relative positioning methods.

<img src="docs/pinlayout-relative.png" width="600"/>
	viewC.pin.top().after(of: viewA).before(of: viewB).margin(10)

This is an equivalent solution using edges:

	viewC.pin.top().left(to: viewA.edge.right).right(to: viewB.edge.left). margin(10)

This is also an equivalent solution using horizontallyBetween(). See section Layout between other views:

	viewC.pin.horizontallyBetween(viewA, and: viewB, aligned: .top).marginHorizontal(10)
<br/>

<a name="relative_edges_layout_w_alignment"></a>

Layout using Relative Edges and alignment

PinLayout also has methods to position relative to other views but with also the ability to specify an alignment. The view can be layouted relative to one or many relative views.

This is really similar to Relative Edges layout except that here two edges are being layouted.

Methods:

HorizontalAlignment values:

VerticalAlignment values:

:pushpin: Multiple relative views: If for example a call to `below(of: [...], aligned:) specify multiple relative views, the view will be layouted below ALL these views. The alignment will be applied using all relative views.

:pushpin: These methods can layout a view’s relative to any views, even if they don't have the same direct superview/parent! It works with any views that have a shared ancestor.

Usage examples:
	view.pin.above(of: view2, aligned: .left)
	view.pin.below(of: [view2, view3, view4], aligned: .left)
	view.pin.after(of: view2, aligned: .top).before(of: view3, aligned: .bottom)
Example:

The following example layout the view B below the view A aligned on its center.

<img src="docs/pinlayout-relative-with-alignment.png" width="600"/>
	viewB.pin.below(of: viewA, aligned: .center)

This is an equivalent solution using anchors:

	viewB.pin.topCenter(to: viewA.anchor.bottomCenter)
Example:

The following example layout the view A below the UIImageView and the UILabel. View A should be left aligned to the UIImageView and right aligned to the UILabel, with a top margin of 10 pixels.

<img src="docs/pinlayout-relative-multi.png" width="600"/>
	a.pin.below(of: [imageView, label], aligned: .left).right(to: label.edge.right).marginTop(10)

This is an equivalent solutions using other methods:

   let maxY = max(imageView.frame.maxY, label.frame.maxY)  // Not so nice
   a.pin.top(maxY).left(to: imageView.edge.left).right(to: label.edge.right).marginTop(10)

Positioning using only visible relative Views

All PinLayout's relative methods can accept an array of Views (ex: below(of: [UIView])). Using these methods its possible to filter the list of relative Views before the list is used by PinLayout.

You can define your own filter methods, but PinLayout has a filter method called visible that can be used to layout a view related to only visible views. This can be really useful when some views may be visible or hidden depending on the situation.

   view.pin.below(of: visible([ageSwitch, ageField])).horizontally().

Note that the Form example use this filter method, see Examples App.

<br/>

<a name="layout_between"></a>

Layout between other views

PinLayout has methods to position a view between two other views, either horizontally or vertically. These methods layout 2 edges simultaneously.

Methods:

:pushpin: These methods can use references to any views, even if they don't have the same direct superview/parent! It works with any views that have a shared ancestor.

Usage examples:
	view.pin.horizontallyBetween(viewA, and: viewB)
	view.pin.verticallyBetween(viewC, and: viewD)
Example:

This example position a view between two other views horizontally with a left and right margins of 5 pixels, and set its top edge at 10 pixels.

<img src="docs/images/pinlayout_horizontallyBetween.png" width="600"/>
   view.pin.horizontallyBetween(viewA, and: viewB).top(10).marginHorizontal(5)

Note that the same result can also be achieved using an alignment parameter, describe in the next section:

   view.pin.horizontallyBetween(viewA, and: viewB, aligned: .top).marginHorizontal(5)

Or using Relative Edges layout:

   view.pin.after(of: viewA).before(of: viewB).top(10).marginHorizontal(5)

<a name="layout_between_w_alignment"></a>

Layout between other views with alignment

PinLayout has also methods to position a view between two other views, either horizontally or vertically, but with also the ability to specify an alignment.

This is really similar to the previous section methods except that here an alignment is specified and three edges are being layouted simultaneously.

Methods:

:pushpin: These methods will apply the alignment related to the first specified reference view. If you want to align it using the second reference view, simply swap views parameters.

:pushpin: These methods can use references to any views, even if they don't have the same direct superview/parent! It works with any views that have a shared ancestor.

HorizontalAlignment values:

VerticalAlignment values:

Usage examples:
	view.pin.horizontallyBetween(viewA, and: viewB, aligned: .top)
	view.pin.verticallyBetween(viewC, and: viewD, aligned: .center)
Example:

This example position a view between two other views vertically, and center it relative to the first view with an top and bottom margin of 10 pixels.

<img src="docs/images/pinlayout_verticallyBetween.png" width="600"/>
   view.pin.verticallyBetween(viewA, and: viewB, aligned: .center).marginVertical(10)
<br/>

<a name="edges"></a>

Edges

PinLayout UIView’s edges

PinLayout adds edges properties to UIView/NSView. These properties are used to reference other view’s edges.

PinLayout View’s edges:

<img src="docs/pinlayout-edges.png" width="620"/>

Layout using edges

PinLayout has methods to attach a View's edge (top, left, bottom, right, start or end edge) to another view’s edge.

Methods:

:pushpin: These methods can pin a view’s edge to any other view's edge, even if they don't have the same direct superview! It works with any views that have a shared ancestor.

Usage examples:
	view.pin.left(to: view1.edge.right)
	view.pin.left(to: view1.edge.right).top(to: view2.edge.right)
Example 1:

This example layout the view B left edge on the view A right edge. It only changes the view B left coordinate.

<img src="docs/example-edges.png" width="620"/>
	viewB.pin.left(to: viewA.edge.right)
Example 2:

This example center horizontally the view B inside the view A with a top margin of 10 from the same view. <img src="docs/pinlayout_example_hCenter_edge.png" width="620"/>

    aView.pin.top(to: bView.edge.top).hCenter(to: bView.edge.hCenter).marginTop(10)
<br/>

<a name="anchors"></a>

Anchors

PinLayout View’s anchors

PinLayout adds anchors properties to UIView/NSView. These properties are used to reference other view’s anchors.

PinLayout View’s anchors:

<img src="docs/pinlayout-anchors.png" width="600"/>

Layout using anchors

PinLayout can use anchors to position view’s related to other views.

Following methods position the corresponding view anchor on another view’s anchor.

Methods:

:pushpin: These methods can pin a view’s anchor to any other view's anchor, even if they don't have the same direct superview! It works with any views that have a shared ancestor.

Usage examples:
    view.pin.topCenter(to: view1.anchor.bottomCenter)
    view.pin.topLeft(to: view1.anchor.topLeft).bottomRight(to: view1.anchor.center)
Example 1:

Layout using an anchor. This example pins the view B topLeft anchor on the view A topRight anchor.

<img src="docs/example-anchors.png" width="620"/>
	viewB.pin.topLeft(to: viewA.anchor.topRight)
Example 2:

This example center the view B on the view A's top-right anchor.

<img src="docs/images/pinlayout_example_anchor_center.png" width="620"/>
	viewB.pin.center(to: viewA.anchor.topRight)
Example 3:

Layout using multiple anchors. It is also possible to combine two anchors to pin the position and the size of a view. The following example will position the view C between the view A and B with horizontal margins of 10px.

<img src="docs/example-multiple-anchors.png" width="620"/>
	viewC.pin.topLeft(to: viewA.anchor.topRight)
	         .bottomRight(to: viewB.anchor.bottomLeft).marginHorizontal(10)

This is an another possible solution using horizontallyBetween():

	viewC.pin.horizontallyBetween(viewA, and: viewB, aligned: .top).height(of: viewA).marginHorizontal(10)
<br/>

<a name="width_height_size"></a>

Width, height and size

Adjust view width, height and size

PinLayout has methods to set the view’s height and width.

Methods:

:pushpin: width/height/size have a higher priority than edges and anchors positioning.

Usage examples:
	view.pin.width(100)
	view.pin.width(50%)
	view.pin.width(of: view1)
	
	view.pin.height(200)
	view.pin.height(100%).maxHeight(240)
	
	view.pin.size(of: view1)
	view.pin.size(50%)
	view.pin.size(250)
<br/>

<a name="adjusting_size"></a>

Adjusting size

PinLayout has methods to adjust the view’s size based on their content.

The resulting size will always respect minWidth/maxWidth/minHeight/maxHeight values.

Methods:

Usage examples:
     // Adjust the view's size based on the result of `UIView.sizeToFit()` and center it.
     view.pin.center().sizeToFit()

     // Adjust the view's size based on a width of 100 pixels.
     // The resulting width will always match the pinned property `width(100)`.
     view.pin.width(100).sizeToFit(.width)
 
     // Adjust the view's size based on view's current width.
     // The resulting width will always match the view's original width.
     // The resulting height will never be bigger than the specified `maxHeight`.
     view.pin.sizeToFit(.width).maxHeight(100)
 
     // Adjust the view's size based on 100% of the superview's height.
     // The resulting height will always match the pinned property `height(100%)`.
     view.pin.height(100%).sizeToFit(.height)
 
    // Adjust the view's size based on view's current height.
    // The resulting width will always match the view's original height.
    view.pin.sizeToFit(.height)

    // Since `.widthFlexible` has been specified, its possible that the resulting
    // width will be smaller or bigger than 100 pixels, based of the label's sizeThatFits()
    // method result.
    label.pin.width(100).sizeToFit(.widthFlexible)
Example:

The following example layout the UILabel on the right side of the UIImageView with a margin of 10px all around and also adjust the UILabel’t height to fit the text size. Note that the UILabel’s height has changed to fit its content.

<img src="docs/images/pinlayout-sizeToFit.png" width="580"/>
	label.pin.after(of: image, aligned: .top).right().marginHorizontal(10).sizeToFit(.width)
<br/>

<a name="minmax_width_height_size"></a>

minWidth, maxWidth, minHeight, maxHeight

PinLayout has methods to set the view’s minimum and maximum width, and minimum and maximum height.

:pushpin: minWidth/maxWidth & minHeight/maxHeight have the highest priority. Higher than sizes (width/height/size, sizeToFit, aspectRatio) and edges positioning (top/left/bottom/right). Their values are always fulfilled.

Methods:

Usage examples:
	view.pin.left(10).right(10).maxWidth(200)
	view.pin.width(100%).maxWidth(250)
	
	view.pin.top().bottom().maxHeight(100)
	view.pin.top().height(50%).maxHeight(200)
Example:

This example layout a view 20 pixels from the top, and horizontally from left to right with a maximum width of 200 pixels. If the superview is smaller than 200 pixels, the view will take the full horizontal space, but for a larger superview, the view will be centered.

<img src="docs/pinlayout-example-maxWidth.png" width="670"/>
   viewA.pin.top(20).hCenter().width(100%).maxWidth(200)

This is an equivalent solutions using the justify() method. This method is explained in the next section:

   viewA.pin.top(20).horizontally().maxWidth(200).justify(.center)
<br/>

<a name="margins"></a>

Margins

PinLayout has methods to apply margins. PinLayout applies margins similar to CSS.

Methods:

Usage examples:
	view.pin.top().left().margin(20)
	view.pin.bottom().marginBottom(20)
	view.pin.horizontally().marginHorizontal(20)
	view.pin.all().margin(10, 12, 0, 12)
<br>

PinLayout margin rules

The following section explains how CSS/PinLayout margin rules are applied.

When and how horizontal margins are applied in PinLayout?

This table explains how and when left and right margins are applied depending on which view’s attribute has been pinned.

View’s pinned attributesLeft MarginRight Margin
LeftMove view right-
Left and WidthMove view right-
Right-Move view left
Right and Width-Move view left
Left and RightReduce the width to apply the left marginReduce the width to apply the right margin
hCenterMove view rightMovie view left

NOTE: - indicates that the margin is not applied.

<br>

When and how does vertical margins are applied in PinLayout?

This table explains how and when top and bottom margins are applied depending on which view’s attribute has been pinned.

View’s pinned attributesTop MarginBottom Margin
TopMove view down-
Top and HeightMove view down-
Bottom-Move view up
Bottom and Height-Move view up
Top and BottomReduce the height to apply the top marginReduce the height to apply the bottom margin
vCenterMove view downMovie view up
<br>

Margin examples

Example 1:

In this example, only the left margin is applied <img src="docs/pinlayout-margin-01.png" width="540"/>

	view.pin.left().margin(10)
Example 2:

In this example, only the right margin is applied <img src="docs/pinlayout-margin-02.png" width="540"/>

	view.pin.right().width(100).marginHorizontal(10)
Example 3:

In this example, the left and right margins are applied

<img src="docs/pinlayout-margin-03.png" width="540"/>
	view.pin.left().right().margin(10)
Example 4:

In this example, left, right and top margins are applied. Note that the view’s width has been reduced to apply left and right margins.

<img src="docs/pinlayout-margin-04.png" width="540"/>
	view.pin.top().left().right().height(100).margin(10)
Example 5:

In this example, left, right, top and bottom margins are applied.

<img src="docs/pinlayout-margin-05.png" width="540"/>
	view.pin.top().bottom().left().right().margin(10)
<br>

pinEdges() and margins

The pinEdges() method pins the four edges (top, left, bottom and right edges) before applying margins.

This method is useful in situations where the width and/or the height attributes have been pinned. This method is an add-on, there is no equivalent in CSS.

Example without pinEdges

Without pinEdges() margins rules would be applied and the view would be moved to the left.

<img src="docs/pinlayout-margin-pinEdges-01.png" width="540"/>
	view.pin.left().width(100%).marginHorizontal(20)
Example with pinEdges

With pinEdges() the left and right margins are applied even if only the left and width has been set. The reason is the call to pinEdges() has pinned the two horizontal edges at their position before applying margins.

<img src="docs/pinlayout-margin-pinEdges-02.png" width="540"/>
	view.pin.left().width(100%).pinEdges().marginHorizontal(20)
<br>

NOTE: In that in that particular situation, the same results could have been achieved differently too:

<img src="docs/pinlayout-margin-pinEdges-03.png" width="540"/>
	view.pin.left().right().marginHorizontal(20)
</br>

<a name="aspect_ratio"></a>

Aspect Ratio

Set the view aspect ratio. AspectRatio solves the problem of knowing one dimension of an element and an aspect ratio, this is particularly useful for images.

AspectRatio is applied only if a single dimension (either width or height) can be determined, in that case the aspect ratio will be used to compute the other dimension.

Methods:

Usage examples:
	aView.pin.left().width(100%).aspectRatio(2)
	imageView.pin.left().width(200).aspectRatio()
Example:

This example layout an UIImageView at the top and center it horizontally, it also adjust its width to 50%. The view’s height will be adjusted automatically using the image aspect ratio.

<img src="docs/pinlayout_example_aspectratio.png" width="540"/>
   imageView.pin.top().hCenter().width(50%).aspectRatio()
</br>

<a name="safeAreaInsets"></a>

safeArea, readable, layout and keyboard margins

UIKit expose 4 kind of areas/guides that can be used to layout views. PinLayout expose them using these properties:

  1. UIView.pin.safeArea: Expose UIKit UIView.safeAreaInsets / UIView.safeAreaLayoutGuide.
  2. UIView.pin.readableMargins: Expose UIKit UIView.readableContentGuide.
  3. UIView.pin.layoutMargins: Expose UIKit UIView.layoutMargins / UIView.layoutMarginsGuide.
  4. UIView.pin.keyboardArea: Expose UIKit UIView.keyboardLayoutGuide. [iOS 15+]

The following image display the 3 areas on an iPad in landscape mode. (safeArea, readableMargins, layoutMargins)

<img src="docs/images/pinlayout_example_layout_margins_landscape.png" width="440" />

See the SafeArea & readableMargins example in the Examples App.

1. pin.safeArea

PinLayout can handle easily iOS 11 UIView.safeAreaInsets, but it goes further by supporting safeAreaInsets for previous iOS releases (including iOS 7/8/9/10) by adding a property UIView.pin.safeArea. PinLayout also extends the support of UIView.safeAreaInsetsDidChange() callback on iOS 7/8/9/10.

Property:
Usage examples:
   // Layout from a UIView
   view.pin.all(pin.safeArea)             // Fill the parent safeArea
   view.pin.top(pin.safeArea)             // Use safeArea.top to position the view
   view.pin.left(pin.safeArea.left + 10)  // Use safeArea.left plus offset of 10 px
   view.pin.horizontally(pin.safeArea)    // Fill horizontally the parent safeArea
	
   // Layout from a UIViewController(), you access 
   // its view safeArea using 'view.pin.safeArea'.
   button.pin.top(view.pin.safeArea)
UIView.safeAreaInsetsDidChange():
Example using UIView.pin.safeArea

This example layout 4 subviews inside the safeArea. The UINavigationBar and UITabBar are translucent, so even if the container UIView goes under both, we can use its UIView.pin.safeArea to keeps its subviews within the safeArea.

<img src="docs/images/pinlayout_safearea_example_iphonex.png" width="540"/>
   topTextLabel.pin.top(pin.safeArea.top + 10).hCenter()
   iconImageView.pin.hCenter().vCenter(-10%)
   textLabel.pin.below(of: iconImageView).hCenter().width(60%).maxWidth(400).sizeToFit(.width).marginTop(20)
   scanButton.pin.bottom(pin.safeArea).hCenter().width(80%).maxWidth(300).height(40)

This example runs perfectly on a iPhone X (iOS 11), but it also runs on any devices with iOS 7, 8, 9 and 10.

:pushpin: This example is available in the Examples App. See example complete source code

<br/>

<a name="readableMargins"></a>

2. pin.readableMargins

Property:
Usage examples:
	label.pin.horizontally(pin.readableMargins)   // the label fill horizontally the readable area.
	view.pin.all(container.pin.readableMargins)   // the view fill its parent's readable area.
	view.pin.left(pin.readableMargins)

:pushpin: The Examples App contains some examples using pin.readableMargins.

<br/>

3. pin.layoutmargins

Property:
Usage example:
	view.pin.left(container.pin.layoutmargins)
	view.pin.left(container.layoutmargins)     // Similar to the previous line
<br/>

4. pin.keyboardArea:

Property:
Usage example:
   container.pin.bottom(view.pin.keyboardArea.top)

<a name="wrapContent"></a>

WrapContent

The following methods are useful to adjust view's width and/or height to wrap all its subviews. These methods also adjust subviews position to create a tight wrap.

Methods:

Types:

Usage examples:
	view.pin.wrapContent().center()   // wrap all subviews and centered the view inside its parent.
	view.pin.wrapContent(padding: 20) // wrap all subviews with a padding of 20 pixels all around
	view.pin.wrapContent(.horizontally)
Example:

This example show the result of different wrapContent() method calls.
Here is the initial state:

<img src="docs/images/wrapContent_before.png" width="200"/>
Source codeResultDescription
view.pin.wrapContent()<img src="docs/images/wrapContent_all.png" width="200"/>Adjust the view's height and width to tight fit its subviews.
view.pin.wrapContent(padding: 10)<img src="docs/images/wrapContent_padding.png" width="200"/>Adjust the view's height and width and add a padding of 10 pixels around its subviews.
view.pin.wrapContent(.horizontally)<img src="docs/images/wrapContent_horizontally.png" width="200"/>Adjust only the view's width.
view.pin.wrapContent(.vertically)<img src="docs/images/wrapContent_vertically.png" width="200"/>Adjust only the view's height.
Example:

This example shows how a view (containerView) that has subviews (imageView and label) can be adjusted to the size of its subviews and then centered inside its parent.

<img src="docs/images/wrapContent_example.png" width="540"/>
   label.pin.below(of: imageView, aligned: .center).marginTop(4)
   containerView.pin.wrapContent(padding: 10).center()
<br/>

<a name="justify_align"></a>

justify() / align()

Methods:

Usage examples:
	view.pin.horizontally().marginHorizontal(20).maxWidth(200).justify(.center)
	view.pin.below(of: A).above(of: B).width(40).align(.center)
Example:

This example layout a view between its superview left and right edges with a maximum size of 200 pixels. Without the usage of the justify(:HorizontalAlign) method, the view will be justified on the left:

<img src="docs/pinlayout-example-justify-left.png" width="540"/>
   viewA.pin.horizontally().maxWidth(200)

The same example, but using justify(.center):

<img src="docs/pinlayout-example-justify-center.png" width="540"/>
   viewA.pin.horizontally().maxWidth(200).justify(.center)

And finally using justify(.right):

<img src="docs/pinlayout-example-justify-right.png" width="540"/>
   viewA.pin.horizontally().maxWidth(200).justify(.right)
Example:

This example centered horizontally the view B in the space remaining at the right of the view A. The view B has a width of 100 pixels.

<img src="docs/pinlayout-example-justify-remaining-space.png" width="540"/>
   viewB.pin.after(of: viewA, aligned: .top).right().width(100).justify(.center)
<br/>

<a name="automatic_sizing"></a>

Automatic Sizing (UIView only)

Sizing views as part of the manual layout process is made with sizeThatFits(_ size: CGSize) where a view returns its ideal size given his parent size. Implementing sizing code has always been cumbersome because you always end up writing the same code twice, a first time for the layout and the second time for sizing. Sizing usually use the same rules layout does but implemented slightly differently because no subview frame should be mutated during sizing. Since PinLayout already takes care of the layout, it makes perfect sense to leverage it's layout engine to compute sizes.

Traditional example:
    override func layoutSubviews() {
        super.layoutSubviews()
        scrollView.pin.all()
        imageView.pin.top().horizontally().sizeToFit(.width).margin(margin)
        textLabel.pin.below(of: imageView).horizontally().sizeToFit(.width).margin(margin)
        scrollView.contentSize = CGSize(width: scrollView.bounds.width, height: textLabel.frame.maxY + margin)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        let availableSize = CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)
        return CGSize(width: size.width, height:
        imageView.sizeThatFits(availableSize).height +
            margin +
            textLabel.sizeThatFits(availableSize).height +
            margin
        )
    }
Usage examples:
    override func layoutSubviews() {
        super.layoutSubviews()
        performLayout()
        didPerformLayout()
    }

    private func performLayout() {
        scrollView.pin.all()
        imageView.pin.top().horizontally().sizeToFit(.width).margin(margin)
        textLabel.pin.below(of: imageView).horizontally().sizeToFit(.width).margin(margin)
    }
    
    private func didPerformLayout() {
        scrollView.contentSize = CGSize(width: scrollView.bounds.width, height: textLabel.frame.maxY + margin)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        autoSizeThatFits(size, layoutClosure: performLayout)
    }

By calling autoSizeThatFits with the given available size and a layout closure, any layouting performed by PinLayout in that closure will be computed without affecting any subview's frame in the view hierarchy. On the other hand, any non PinLayout related code will also be executed. For that reason, it is really important to separate your layout code in it's own function to avoid any side effect during sizing, like setting the scroll view's content size in the above exemple or perhaps assigning itemSize in a collection view layout. That kind of code that depends on the layout should only be executed when layoutSubviews() is called as part of a normal layout pass.

The resulting size also takes into account the margins applied on subviews, even on the bottom and trailing sides. Automatic sizing makes it really easy to write your layout logic once and add proper sizing behavior with virtually no additional effort.

An Automatic Sizing example is available in the Examples App.

Notes:

  1. Automatic Sizing is currently only available on iOS.
  2. Automatic Sizing is still in beta, so any comments are welcomed.
<br/>

<a name="uiview_transform"></a>

UIView's transforms

UIView.pin versus UIView.pinFrame

Until now UIView.pin was used to layout views, but there's also another property called UIView.pinFrame that does something slightly different in situations where the view has a transform (UIView.transform, scaling, rotation, ...).

Examples

The following examples use this view initial size and position:

<img src="docs/images/pinlayout_transform_orig.png" width="160"/>
Example using a Rotation transform

Using pin:

  view.transform = .init(rotationAngle: CGFloat.pi / 2)
  view.pin.center().width(100).height(50)

Using pinFrame:

  view.transform = .init(rotationAngle: CGFloat.pi / 2)
  view.pinFrame.center().width(100).height(50)
Result using pinresult using pinFrame
Rotation transform<img src="docs/images/pinlayout_transform_rotate_pin.png" width="160"/><img src="docs/images/pinlayout_transform_rotate_pinFrame.png" width="160"/>

Layout relative to a view with a transform

When layouting a view relative to a view with a transform (ex: below(of:UIView), top(to edge: ViewEdge), ...), pin and pinFrame react also differently.

Example using a Scale transform

In the following example the View A have a scale transform of 1.5x1.5. The view B is layouted below the view A.

Using pin:

  aView.transform = .init(scaleX: 1.5, y: 1.5)
  aView.pin.width(100).height(50)
  bView.pin.below(of: aView, aligned: .left)

Using pinFrame:

aView.transform = .init(scaleX: 1.5, y: 1.5)
aView.pin.width(100).height(50)
bView.pinFrame.below(of: aView, aligned: .left)
Result using pinresult using pinFrame
Scale transform<img src="docs/images/pinlayout_relative_transform_scale_pin.png" width="160"/><img src="docs/images/pinlayout_relative_transform_scale_pinFrame.png" width="160"/>
</br>

<a name="warnings"></a>

Warnings

PinLayout's warnings

PinLayout can display warnings in the console when pin rules cannot be applied or are invalid.

Here a list of fews warning:

Enabling/Disabling warnings

Property:

Enabling/Disabling warnings individually

Few individual warnings can also be enabled/disabled individually:

<br/>

PinLayout style guide

:pushpin: PinLayout's method call order is irrelevant, the layout result will always be the same.

<br/>

<a name="animations"></a>

Animations using PinLayout

PinLayout can easily animates Views. Multiple strategies can be used to animate layout using PinLayout.

See the section Animations using PinLayout for more details

The following animation example is available in the Examples App.

<a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/Animations/AnimationsView.swift"><img src="docs/images/example-animations.gif" width=120/></a>

<br/>

<a name="more_examples"></a>

More examples

Adjust to container size

The following examples show how PinLayout can be used to adjust views size and position to the size of their container. In this case containers are cells.

<img src="docs/pinlayout-example-cells.png" width="500"/>

Cell A:

   a1.pin.vertically().left().width(50)
   a2.pin.after(of: a1, aligned: .top).bottomRight().marginLeft(10)

Cell B:

   b2.pin.vertically().right().width(50)
   b1.pin.before(of: b2, aligned: .top).bottom().left().marginRight(10)

Cell C:

   c2.pin.vertically().hCenter().width(50)
   c1.pin.before(of: c2, aligned: .top).bottom().left().marginRight(10)
   c3.pin.after(of: c2, aligned: .top).bottom().right().marginLeft(10)

Cell D:

   d1.pin.vertically().left().width(25%)
   d2.pin.after(of: d1, aligned: .top).bottom().width(50%).marginLeft(10)
   d3.pin.after(of: d2, aligned: .top).bottom().right().marginLeft(10)
<br>

<a name="installation"></a>

Installation

CocoaPods

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

    pod 'PinLayout'

Then, run pod install.

Swift Package Manager (SPM)

  1. From Xcode, select from the menu File > Swift Packages > Add Package Dependency
  2. Specify the URL https://github.com/layoutBox/PinLayout

Carthage

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

github "layoutBox/PinLayout"

Then, run carthage update to build the framework and drag the built PinLayout.framework into your Xcode project.

<br>

<a name="examples_app"></a>

Examples App

The PinLayout's Example App exposes some usage example of PinLayout.

See the Example App section to get more information

Included examples:

<p align="center"> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/Intro/IntroView.swift"><img src="docs/images/pinlayout_intro_example_iphonex.png" width=420/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/AdjustToContainer/Subviews/ChoiceSelectorView.swift"><img src="docs/pinlayout_example_adjust_to_container-portrait.png" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/TableViewExample/TableViewExampleView.swift"><img src="docs/pinlayout_exampleapp_tableview.png" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/CollectionViewExample/HouseCell.swift"><img src="docs/pinlayout_exampleapp_collectionview.png" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/Animations/AnimationsView.swift"><img src="docs/images/example-animations.gif" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/Form/FormView.swift"><img src="docs/pinlayout_example_form.gif" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/AutoAdjustingSize/AutoAdjustingSizeView.swift"><img src="docs/pinlayout_exampleapp_auto_adjusting_size.png" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/RelativeView/RelativeView.swift"><img src="docs/pinlayout_exampleapp_relative_position.png" width=120/> </a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/BetweenView/BetweenView.swift"><img src="docs/pinlayout_exampleapp_multi_relative_position.png" width=120/></a> <a href="https://github.com/layoutBox/PinLayout/blob/master/Example/PinLayoutSample/UI/Examples/AutoSizing/AutoSizingContainerView.swift"><img src="docs/pinlayout_exampleapp_automatic_sizing.png" width=120/></a> </p> <br>

<a name="macos_support"></a>

macOS Support

PinLayout can layout NSView's on macOS. All PinLayout's properties and methods are available, with the following exceptions:

<br>

<a name="calayer_support"></a>

CALayer Support

PinLayout can layouts CALayer's. All PinLayout's properties and methods are available, with the following exceptions:

Usage Examples:
aLayer = CALayer()
bLayer = CALayer()
view.layer.addSublayer(aLayer)
view.layer.addSublayer(bLayer)
...

aLayer.pin.top(10).left(10).width(20%).height(80%)
bLayer.pin.below(of: aLayer, aligned: .left).size(of: aLayer)
<br>

<a name="playgrounds"></a>

PinLayout in Xcode Playgrounds

PinLayout layouts views immediately after the line containing .pin has been fully executed, thanks to ARC (Automatic Reference Counting) this works perfectly on iOS/tvOS/macOS simulators and devices. But in Xcode Playgrounds, ARC doesn't work as expected, object references are kept much longer. This is a well documented issue and have a little impact on the PinLayout behaviour.

See here for more details about using PinLayout in Xcode playgrounds

<br>

<a name="objective_c_interface"></a>

PinLayout using Objective-C

PinLayout also expose an Objective-C interface slightly different than the Swift interface.

See here for more details

<br>

<a name="faq"></a>

FAQ

All example in the Examples App handle correctly the safeAreaInsets and works on iPhone X in landscape mode. Many PinLayout's method accept an UIEdgeInsets as parameter.

Note that only the UIViewController's main view must handle the safeAreaInsets, sub-views don't have to handle it.

<br>

<a name="comments"></a>

Questions, comments, ideas, suggestions, issues, ....

If you have questions, you can checks already answered questions here.

For any comments, ideas, suggestions, issues, simply open an issue.

If you find PinLayout interesting, thanks to Star it. You'll be able to retrieve it easily later.

If you'd like to contribute, you're welcome!

Thanks

PinLayout was inspired by other great layout frameworks, including:

History

PinLayout recent history is available in the CHANGELOG also in GitHub Releases.

Recent breaking change

License

MIT License