Fast Swift Views layouting without auto layout. No magic, pure code, full control and blazing fast

Overview

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

  • iOS 9.0+ / tvOS 9.0+ / macOS 10.9+
  • Swift 5.x / 4 / 3 / Objective-C
  • Xcode 12 / 11 / 10

Recent changes/features

Content


📌 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

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.


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.

  • UIImageView's size is 100x100 and layouted below the UINavigationBar with a margin of 10 pixels all around.
  • UISegmentedControl is at the right of the logo image, use the remaining horizontal space with a left and right margin of 20 pixels.
  • UILabel is below the UISegmentedControl with a top margin of 10 pixels. Its width matched the UISegmentedControl's width. The label is multiline, so its height must be adjusted to fit its width.
  • Separator is below the UIImageView and the UILabel, i.e. below the tallest one. The separator has a top margin of 10 pixels, left-aligned to the UIImageView and right-aligned to the UISegmentedControl.

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)
}
  • 4 views, 4 lines
  • PinLayout expose the safeAreaInsets through UIView.pin.safeArea, this property support not only iOS 11, but is also backward compatible for earlier iOS releases (7/8/9/10). See safeAreaInsets support for more information.
  • PinLayout doesn't use auto layout constraints, it is a framework that manually layout views. For that reason you need to update the layout inside either UIView.layoutSubviews() or UIViewController.viewDidLayoutSubviews() to handle container size's changes, including device rotation. You'll also need to handle UITraitCollection changes for app's that support multitasking. In the example above PinLayout's commands are inside UIView's layoutSubviews() method.
  • This example is available in the Examples App. See example complete source code

Example 2:

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

  • If the container's width is smaller than 500 pixels, the label takes the full width and the UISegmentedControl is placed below it.
  • If the container's width is greater or equal to 500 pixels, the UISegmentedControl is at the top-right corner and the label takes the remaining horizontal space.

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

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

PinLayout principles and philosophy

  • Manual layouting (doesn't rely on auto layout).
  • PinLayout exist to be simple and fast as possible! In fact, it is fast as manual layouting. See performance results below.
  • Full control: You're in the middle of the layout process, no magic black box.
  • Layout one view at a time. Make it simple to code and debug.
  • Concise syntax. Layout most views using a single line.
  • See the complete list here....

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.

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

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

Right to left languages (RTL) support

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

See here for more details.


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.

    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.

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

  • top(_ offset: CGFloat) / top(_ offset: Percent) / top() / top(_ margin: UIEdgeInsets)
    Position the top edge. The offset specifies the top edge distance from the superview's top edge in pixels (or in percentage of its superview's height). top() is similar to calling top(0), it position the view top edge directly on its superview top edge. top(:UIEdgeInsets) use the UIEdgeInsets.top property, is particularly useful with safeArea, readable and layout margins.

  • bottom(_ offset: CGFloat) / bottom(_ offset: Percent) / bottom() / bottom(_ margin: UIEdgeInsets)
    Position the bottom edge. The offset specifies the bottom edge distance from the superview's bottom edge in pixels (or in percentage of its superview's height). bottom() is similar to calling bottom(0), it position the view bottom edge directly on its superview top edge. bottom(:UIEdgeInsets) use the UIEdgeInsets.bottom property, it is is particularly useful with safeArea, readable and layout margins.

  • left(_ offset: CGFloat) / left(_ offset: Percent) / left() / left(_ margin: UIEdgeInsets)
    Position the left edge. The offset specifies the left edge distance from the superview's left edge in pixels (or in percentage of its superview's width). left() is similar to calling left(0), it position the view left edge directly on its superview left edge. left(:UIEdgeInsets) use the UIEdgeInsets.left property, it is particularly useful with safeArea, readable and layout margins.

  • right(_ offset: CGFloat) / right(_ offset: Percent) / right() / right(_ margin: UIEdgeInsets)
    Position the right edge. The offset specifies the right edge distance from the superview's right edge in pixels (or in percentage of its superview's width). right() is similar to calling right(0), it position the view right edge directly on its superview right edge. right(:UIEdgeInsets) use the UIEdgeInsets. right property, it is particularly useful with safeArea, readable and layout margins.

  • vCenter(_ offset: CGFloat) / vCenter(_ offset: Percent) / vCenter()
    Position the vertical center (center.y). The offset specifies the distance vertically of the view's center related to the superview's center in pixels (or in percentage of its superview's height). A positive offset move the view down and a negative value move it up relative to the superview's center. vCenter() is similar to calling vCenter(0), it position vertically the view's center directly on its superview vertical center.

  • hCenter(_ offset: CGFloat) / hCenter(_ offset: Percent) / hCenter()
    Position the horizontal center (center.x). The offset specifies the distance horizontally of the view's center related to the superview's center in pixels (or in percentage of its superview's width). A positive offset move the view to the right and a negative offset move it to the left relative to the superview's center. hCenter() is similar to calling hCenter(0), it position horizontally the view's center directly on its superview horizontal center.

Methods supporting LTR (left-to-right) and RTL (right-to-left) languages.
  • start(_ offset: CGFloat) / start(_ offset: Percent) / start() / start(_ margin: UIEdgeInsets) ↔️
    Position the left or right edge depending of the LTR language direction. In LTR direction the offset specifies the left edge distance from the superview's left edge in pixels (or in percentage of its superview's width). In RTL direction the offset specifies the right edge distance from the superview's right edge in pixels (or in percentage of its superview's width).
    start() is similar to calling start(0). start(:UIEdgeInsets) use the UIEdgeInsets.left property in LTR direction and UIEdgeInsets.right in RTL direction.

  • end(_ offset: CGFloat) / end(_ offset: Percent) / end() / end(_ margin: UIEdgeInsets) ↔️
    Position the left or right edge depending of the LTR language direction. In LTR direction the offset specifies the right edge distance from the superview's right edge in pixels (or in percentage of its superview's width). In RTL direction the offset specifies the left edge distance from the superview's left edge in pixels (or in percentage of its superview's width). end() is similar to calling end(0). end(:UIEdgeInsets) use the UIEdgeInsets.right property in LTR direction and UIEdgeInsets.left in RTL direction.

Methods pinning multiple edges:

  • all(_ margin: CGFloat) / all() / all(_ margin: UIEdgeInsets)
    Position the top, left, bottom and right edges. The margin specifies the top, bottom, left and right edges distance from the superview's corresponding edge in pixels. Similar to calling view.top(value).bottom(value).left(value).right(value).
    all() is similar to calling all(0).
    all(:UIEdgeInsets) is particularly useful with safeArea, readable and layout margins.

  • horizontally(_ margin: CGFloat) / horizontally(_ margin: Percent) / horizontally() / horizontally(_ margin: UIEdgeInsets)
    Position the left and right edges. The margin specifies the left and right edges distance from its superview's corresponding edges in pixels (or in percentage of its superview's width).
    horizontally() is similar to calling horizontally(0).
    horizontally(:UIEdgeInsets) use the UIEdgeInsets's left and right value to pin left and right edges.

  • vertically(_ margin: CGFloat) / vertically(_ margin: Percent) / vertically() / vertically(_ margin: UIEdgeInsets)
    Position the top and bottom edges. The margin specifies the top and bottom edges distance from on its superview's corresponding edges in pixels (or in percentage of its superview's height).
    vertically() is similar to calling vertically(0).
    vertically(:UIEdgeInsets) use the UIEdgeInsets's top and bottom value to pin top and bottom 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.

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.

Example:

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

	viewA.pin.topRight().size(100)

This is equivalent to:

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

Methods:

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

  • topLeft(_ offset: CGFloat) / topLeft()
    Position the top and left edges. The offset specifies the distance from their superview's corresponding edges in pixels. topLeft() is similar to calling topLeft(0).

  • topCenter(_ topOffset: CGFloat) / topCenter()
    Position the top and horizontal center (center.x). The offset specifies the top edge distance from the superview's top edge in pixels. topCenter() is similar to calling topCenter(0).

  • topRight(_ offset: CGFloat) / topRight()
    Position the top and right edges. The offset specifies the distance from their superview's corresponding edges in pixels. topRight() is similar to calling topRight(0).

  • centerLeft(_ leftOffset: CGFloat) / centerLeft()
    Position the vertical center (center.y) and the left edge. The offset specifies the left edge distance from the superview's left edge in pixels. centerLeft() is similar to calling centerLeft(0).

  • center(_ offset: CGFloat) / center()
    Position the horizontal and vertical center (center.y). The offset specifies an offset from the superview's center in pixels. center() is similar to calling center(0).

  • centerRight(_ rightOffset: CGFloat) / centerRight()
    Position the vertical center (center.y) and the right edge. The offset specifies the right edge distance from the superview's right edge in pixels. centerRight() is similar to calling centerRight(0).

  • bottomLeft(_ offset: CGFloat) / bottomLeft()
    Position the bottom and left edges. The offset specifies the distance from their superview's corresponding edges in pixels. bottomLeft() is similar to calling bottomLeft(0).

  • bottomCenter(_ bottomOffset: CGFloat) / bottomCenter()
    Position the bottom and horizontal center (center.x). The offset specifies the bottom edge distance from the superview's bottom edge in pixels. bottomCenter() is similar to calling bottomCenter(0).

  • bottomRight(_ offset: CGFloat) / bottomRight()
    Position the bottom and right edges. The offset specifies the distance from their superview's corresponding edges in pixels. bottomRight() is similar to calling bottomRight(0).

Methods supporting LTR (left-to-right) and RTL (right-to-left) languages.
  • topStart(_ offset: CGFloat) / topStart() ↔️
    In LTR direction position the top and left edges. In RTL direction position the top and right edges.

  • topEnd(_ offset: CGFloat) / topEnd() ↔️
    In LTR direction position the top and right edges. In RTL direction position the top and left edges.

  • bottomStart(_ offset: CGFloat) / bottomStart() ↔️
    In LTR direction position the bottom and left edges. In RTL direction position the bottom and right edges.

  • bottomEnd(_ offset: CGFloat) / bottomEnd() ↔️
    In LTR direction position the bottom and right edges. In RTL direction position the bottom and left edges.

  • centerStart(_ offset: CGFloat) / centerStart() ↔️
    In LTR direction position the vertical center (center.y) and the left edge. In RTL direction position the vertical center (center.y) and the right edge.

  • centerEnd(_ offset: CGFloat) / centerEnd() ↔️
    In LTR direction position the vertical center (center.y) and the right edge. In RTL direction position the vertical center (center.y) and the left edge.

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)

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:

  • above(of: UIView) / above(of: [UIView])
    Position the view above the specified view(s). One or many relative views can be specified. This method position the view’s bottom edge.

  • below(of: UIView) / below(of: [UIView])
    Position the view below the specified view(s). One or many relative views can be specified. This method position the view’s top edge.

  • before(of: UIView) / before(of: [UIView]) ↔️
    In LTR direction the view is positioned at the left of the specified view(s). In RTL direction the view is positioned at the right. One or many relative views can be specified.

  • after(of: UIView) / after(of: [UIView]) ↔️
    In LTR direction the view is positioned at the right of the specified view(s). In RTL direction the view is positioned at the left. One or many relative views can be specified.

  • left(of: UIView) / left(of: [UIView])
    Position the view left of the specified view(s). Similar to before(of:). One or many relative views can be specified. This method position the view’s right edge.

  • right(of: UIView) / right(of: [UIView])
    Position the view right of the specified view(s). Similar to after(of:). One or many relative views can be specified. This method position the view’s left edge.

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

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

	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)

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:

  • above(of: UIView, aligned: HorizontalAlignment)
    above(of: [UIView], aligned: HorizontalAlignment)
    Position the view above the specified view(s) and aligned it using the specified HorizontalAlignment. One or many relative views can be specified.

  • below(of: UIView, aligned: HorizontalAlignment)
    below(of: [UIView], aligned: HorizontalAlignment)
    Position the view below the specified view(s) and aligned it using the specified HorizontalAlignment. One or many relative views can be specified.

  • before(of: UIView, aligned: HorizontalAlignment) ↔️
    before(of: [UIView], aligned: HorizontalAlignment) ↔️
    In LTR direction the view is positioned at the left of the specified view(s). In RTL direction the view is positioned at the right. One or many relative views can be specified.

  • after(of: UIView, aligned: HorizontalAlignment) ↔️
    after(of: [UIView], aligned: HorizontalAlignment) ↔️
    In LTR direction the view is positioned at the right of the specified view(s). In RTL direction the view is positioned at the left. One or many relative views can be specified.

  • left(of: UIView, aligned: VerticalAlignment)
    left(of: [UIView], aligned: HorizontalAlignment)
    Position the view left of the specified view(s) and aligned it using the specified VerticalAlignment. Similar to before(of:). One or many relative views can be specified.

  • right(of: UIView, aligned: VerticalAlignment)
    right(of: [UIView], aligned: HorizontalAlignment)
    Position the view right of the specified view(s) and aligned it using the specified VerticalAlignment. Similar to after(of:). One or many relative views can be specified.

HorizontalAlignment values:

  • .left: The view's left edge will be left-aligned with the relative view (or the left most view if a list of relative views is specified).
  • .center: The view's will be horizontally centered with the relative view (or the average hCenter if a list of relative views is used).
  • .right: The view's right edge will be right-aligned with the relative view (or the right most view if a list of relative views is specified).
  • .start ↔️ :
    In LTR direction, similar to using .left. In RTL direction, similar to using .right.
  • .end ↔️ :
    In LTR direction, similar to using .right. In RTL direction, similar to using .left.

VerticalAlignment values:

  • .top: The view's top edge will be top-aligned with the relative view (or the top most view if a list of relative views is specified).
  • .center: The view's will be vertically centered with the relative view (or the average vCenter if a list of relative views is used).
  • .bottom: The view's bottom edge will be bottom-aligned with the relative view (or the bottom most view if a list of relative views is specified).

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

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

	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.

	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.


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:

  • horizontallyBetween(:UIView, and: UIView)
    Position the view between the two specified views horizontally. The method layout the view's left and right edges. The order of the reference views is irrelevant. Note that the layout will be applied only if there is horizontal space between the specified views.

  • verticallyBetween(:UIView, and: UIView)
    Position the view between the two specified views vertically. The method layout the view's top and bottom edges. The order of the reference views is irrelevant. Note that the layout will be applied only if there is vertical space between the specified views.

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

   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)

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:

  • horizontallyBetween(:UIView, and: UIView, aligned: VerticalAlign)
    Position the view between the two specified views horizontally and aligned it using the specified VerticalAlign. The view will be aligned related to the first specified reference view. Note that the layout will be applied only if there is horizontal space between the specified views.

  • verticallyBetween(:UIView, and: UIView, aligned: HorizontalAlign)
    Position the view between the two specified views vertically and aligned it using the specified HorizontalAlign. The view will be aligned related to the first specified reference view. Note that the layout will be applied only if there is vertical space between the specified views.

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

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

  • .left: The view's left edge will be left-aligned with the first view.
  • .center: The view's will be horizontally centered with the first view.
  • .right: The view's right edge will be right-aligned with the first view.
  • .start ↔️ :
    In LTR direction, similar to using .left. In RTL direction, similar to using .right.
  • .end ↔️ :
    In LTR direction, similar to using .right. In RTL direction, similar to using .left.

VerticalAlignment values:

  • .top: The view's top edge will be top-aligned with the first view.
  • .center: The view's will be vertically centered with the first view.
  • .bottom: The view's bottom edge will be bottom-aligned with the first view.
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.

   view.pin.verticallyBetween(viewA, and: viewB, aligned: .center).marginVertical(10)

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:

  • UIView.edge.top
  • UIView.edge.vCenter
  • UIView.edge.bottom
  • UIView.edge.left
  • UIView.edge.hCenter
  • UIView.edge.right
  • UIView.edge.start ↔️
  • UIView.edge.end ↔️

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:

  • top(to edge: ViewEdge):
    Position the view's top edge directly on another view’s edge (top/vCenter/bottom).

  • vCenter(to edge: ViewEdge):
    Position vertically the view's center directly on another view’s edge (top/vCenter/bottom).

  • bottom(to edge: ViewEdge):
    Position the view's bottom edge directly on another view’s edge (top/vCenter/bottom).

  • left(to: edge: ViewEdge):
    Position the view's left edge directly on another view’s edge (left/hCenter/right).

  • hCenter(to: edge: ViewEdge):
    Position horizontally the view's center directly on another view’s edge (left/hCenter/right).

  • right(to: edge: ViewEdge):
    Position the view's right edge directly on another view’s edge (left/hCenter/right).

  • start(to: edge: ViewEdge) ↔️ In LTR direction it position the view's left edge directly on another view’s edge.
    In RTL direction it position the view's right edge directly on another view’s edge.

  • end(to: edge: ViewEdge) ↔️
    In LTR direction it position the view's top edge directly on another view’s edge.
    In RTL direction it position the view's bottom edge directly on another view’s edge.

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

	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.

    aView.pin.top(to: bView.edge.top).hCenter(to: bView.edge.hCenter).marginTop(10)

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:

  • UIView.anchor.topLeft / UIView.anchor.topCenter / UIView.anchor.topRight
  • UIView.anchor.topStart / UIView.anchor.topEnd ↔️
  • UIView.anchor.centerLeft / UIView.anchor.centers / UIView.anchor.centerRight
  • UIView.anchor.centerStart / UIView.anchor.centerEnd ↔️
  • UIView.anchor.bottomLeft / UIView.anchor.bottomCenter / UIView.anchor.bottomRight
  • UIView.anchor.bottomStart / UIView.anchor.bottomEnd ↔️

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:

  • topLeft(to anchor: Anchor)
  • topCenter(to anchor: Anchor)
  • topRight(to anchor: Anchor)
  • topStart(to anchor: Anchor) ↔️
  • topEnd(to anchor: Anchor) ↔️
  • centerLeft(to anchor: Anchor)
  • center(to anchor: Anchor)
  • centerRight(to anchor: Anchor)
  • centerStart(to anchor: Anchor) ↔️
  • centerEnd(to anchor: Anchor) ↔️
  • bottomLeft(to anchor: Anchor)
  • bottomCenter(to anchor: Anchor)
  • bottomRight(to anchor: Anchor)
  • bottomStart(to anchor: Anchor) ↔️
  • bottomEnd(to anchor: Anchor) ↔️

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

	viewB.pin.topLeft(to: viewA.anchor.topRight)
Example 2:

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

	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.

	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)

Width, height and size

Adjust view width, height and size

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

Methods:

  • width(:CGFloat) / width(:Percent)
    The value specifies the view's width in pixels (or in percentage of its superview). The value must be non-negative.

  • width(of: UIView)
    Set the view’s width to match the referenced view’s width.

  • height(:CGFloat) / height(:Percent)
    The value specifies the view's height in pixels (or in percentage of its superview). The value must be non-negative.

  • height(of: UIView)
    Set the view’s height to match the referenced view’s height

  • size(:CGSize) / size(:Percent)
    The value specifies view's width and the height in pixels (or in percentage of its superview). Values must be non-negative.

  • size(_ sideLength: CGFloat)
    The value specifies the width and the height of the view in pixels, creating a square view. Values must be non-negative.

  • size(of: UIView)
    Set the view’s size to match the referenced view’s size

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

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:

  • sizeToFit()
    The method adjust the view's size based on it's content requirements so that it uses the most appropriate amount of space. This fit type has the same effect as calling sizeToFit() on a view. The resulting size come from sizeThatFits(..) being called with the current view bounds. Particularly useful for controls/views that have an intrinsic size (label, button, ...).

  • sizeToFit(: FitType)
    The method adjust the view's size based on the result of the method sizeThatFits(:CGSize).
    PinLayout will adjust either the view's width or height based on the fitType parameter value.
    If margins are specified, they will be applied before calling the view's sizeThatFits(:CGSize) method.

    Parameter fitType: Identify the reference dimension (width / height) that will be used to adjust the view's size.

  • .width: The method adjust the view's size based on the reference width.

    • If properties related to the width have been pinned (e.g: width, left & right, margins, ...), the reference width will be determined by these properties, if not the current view's width will be used.
    • The resulting width will always match the reference width.
  • .height: The method adjust the view's size based on the reference height.

    • If properties related to the height have been pinned (e.g: height, top & bottom, margins, ...), the reference height will be determined by these properties, if not the current view's height will be used.
    • The resulting height will always match the reference height.
  • .widthFlexible: Similar to .width, except that PinLayout won't constrain the resulting width to match the reference width. The resulting width may be smaller of bigger depending on the view's sizeThatFits(..) method result. For example a single line UILabel may returns a smaller width if it's string is smaller than the reference width.

  • .heightFlexible: Similar to .height, except that PinLayout won't constrain the resulting height to match the reference height. The resulting height may be smaller of bigger depending on the view's sizeThatFits(..) method result.

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.

	label.pin.after(of: image, aligned: .top).right().marginHorizontal(10).sizeToFit(.width)

minWidth, maxWidth, minHeight, maxHeight

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

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

  • minWidth(:CGFloat)
    minWidth(:Percent)
    The value specifies the view's minimum width of the view in pixels (or in percentage of its superview). The value must be non-negative.

  • maxWidth(:CGFloat)
    maxWidth(:Percent)
    The value specifies the view's maximum width of the view in pixels (or in percentage of its superview). The value must be non-negative.

  • minHeight(:CGFloat)
    minHeight(:Percent)
    The value specifies the view's minimum height of the view in pixels (or in percentage of its superview). The value must be non-negative.

  • maxHeight(:CGFloat)
    maxHeight(:Percent)
    The value specifies the view's maximum height of the view in pixels (or in percentage of its superview). The value must be non-negative.

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.

   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)

Margins

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

Methods:

  • marginTop(:CGFloat) / marginTop(: Percent)
    Set the top margin in pixels or in percentage of its superview's height.
  • marginLeft(:CGFloat) / marginLeft(: Percent)
    Set the left margin in pixels or in percentage of its superview's width.
  • marginBottom(:CGFloat) / marginBottom(: Percent)
    Set the bottom margin in pixels or in percentage of its superview's height
  • marginRight(:CGFloat) / marginRight(: Percent)
    Set the right margin in pixels or in percentage of its superview's width.
  • marginStart(:CGFloat) / marginStart(: Percent) ↔️
    Set the start margin. Depends on the value of Pin.layoutDirection(...). In LTR direction, start margin specify the left margin. In RTL direction, start margin specify the right margin.
  • marginEnd(:CGFloat) / marginEnd(: Percent) ↔️
    Set the end margin. Depends on the value of Pin.layoutDirection(...). In LTR direction, end margin specify the right margin. In RTL direction, end margin specify the left margin.
  • marginHorizontal(:CGFloat) / marginHorizontal(: Percent)
    Set the left, right, start and end margins to the specified value
  • marginVertical(:CGFloat) / marginVertical(: Percent)
    Set the top and bottom margins to the specified value.
  • margin(:CGFloat) / margin(: Percent)
    Apply the value to all margins (top, left, bottom, right), in pixels or in percentage of its superview's width/height.
  • margin(:UIEdgeInsets)
    Set all margins using an UIEdgeInsets. This method is particularly useful to set all margins using safeArea, readable and layout margins.
  • margin(_ insets: NSDirectionalEdgeInsets)
    Set all margins using an NSDirectionalEdgeInsets. This method is useful to set all margins using iOS 11 UIView. directionalLayoutMargins when layouting a view supporting RTL/LTR languages.
  • margin(_ vertical: CGFloat, _ horizontal: CGFloat)
    margin(_ vertical: Percent, _ horizontal: Percent)
    Set the individually vertical margins (top, bottom) and horizontal margins (left, right, start, end)
  • margin(_ top: CGFloat, _ horizontal: CGFloat, _ bottom: CGFloat)
    margin(_ top: Percent, _ horizontal: Percent, _ bottom: Percent)
    Set individually top, horizontal margins and bottom margin
  • margin(_ top: CGFloat, _ left: CGFloat, _ bottom: CGFloat, _ right: CGFloat)
    margin(_ top: Percent, _ left: Percent, _ bottom: Percent, _ right: Percent)
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)

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 attributes Left Margin Right Margin
Left Move view right -
Left and Width Move view right -
Right - Move view left
Right and Width - Move view left
Left and Right Reduce the width to apply the left margin Reduce the width to apply the right margin
hCenter Move view right Movie view left

NOTE: - indicates that the margin is not applied.


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 attributes Top Margin Bottom Margin
Top Move view down -
Top and Height Move view down -
Bottom - Move view up
Bottom and Height - Move view up
Top and Bottom Reduce the height to apply the top margin Reduce the height to apply the bottom margin
vCenter Move view down Movie view up

Margin examples

Example 1:

In this example, only the left margin is applied

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

In this example, only the right margin is applied

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

In this example, the left and right margins are applied

	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.

	view.pin.top().left().right().height(100).margin(10)
Example 5:

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

	view.pin.top().bottom().left().right().margin(10)

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.

	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.

	view.pin.left().width(100%).pinEdges().marginHorizontal(20)

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

	view.pin.left().right().marginHorizontal(20)

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.

  • AspectRatio is defined as the ratio between the width and the height (width / height).
  • An aspect ratio of 2 means the width is twice the size of the height.
  • AspectRatio respects the min (minWidth/minHeight) and the max (maxWidth/maxHeight) dimensions of an item.

Methods:

  • aspectRatio(_ ratio: CGFloat):
    Set the view aspect ratio using a CGFloat. AspectRatio is defined as the ratio between the width and the height (width / height).

  • aspectRatio(of view: UIView):
    Set the view aspect ratio using another UIView's aspect ratio.

  • aspectRatio():
    If the layouted view is an UIImageView, this method will set the aspectRatio using the UIImageView's image dimension. For other types of views, this method as no impact.

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.

   imageView.pin.top().hCenter().width(50%).aspectRatio()

safeArea, readable and layout margins

UIKit expose 3 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.

The following image display the 3 areas on an iPad in landscape mode.

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:
  • UIView.pin.safeArea
    The safe area of a view represent the area not covered by navigation bars, tab bars, toolbars, and other ancestors that obscure a view controller's view.

    PinLayout expose the view's safeAreaInsets through UIView.pin.safeArea, this property is available on iOS 11, but also on iOS 7/8/9/10! This gives you immediately the opportunity to use this property for any iOS releases. UIView.pin.safeArea is available even if you don't use PinLayout to layout your views.

    While running on a iOS 11 devices, this property simply expose the UIKit UIView.safeAreaInsets. But on previous iOS releases, PinLayout use the information from the UIViewController's topLayoutGuide and bottomLayoutGuide to compute the safeArea.

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():
  • iOS 11 has also introduced the method UIView.safeAreaInsetsDidChange() which is called when the safe area of the view changes. This method is called only when your app runs on a iOS 11 device. PinLayout's extend that and support this method also on older iOS releases including iOS 9/10.

  • Note that if you handle the layout from UIView.layoutSubviews() or UIViewController.viewDidLayoutSubviews(), you probably won't need to implement safeAreaInsetsDidChange(). By default layout are invalidated and these methods are called when the safeAreaInsets changes.

  • Controlling PinLayout UIView.safeAreaInsetsDidChange() calls:
    You can control how PinLayout calls UIView.safeAreaInsetsDidChange() for iOS 7/8/9/10 (by default iOS 11 natively calls this method).

    The property Pin.safeAreaInsetsDidChangeMode supports 3 modes:

    • always: (Default mode) In this mode, PinLayout will call your views safeAreaInsetsDidChange() method automatically for iOS releases 7/8/9/10.

       Pin.safeAreaInsetsDidChangeMode = .always // Default mode
       ...
       
       class CustomerView: UIView {
          override func safeAreaInsetsDidChange() {
            // This method will be called on iOS 11, but also on iOS 7/8/9/10 
            // because "Pin.safeAreaInsetsDidChangeMode" has been set to ".always".
            if #available(iOS 11.0, *) {
               super.safeAreaInsetsDidChange()
            }
            ...
         }
      }
    • optIn: (Default mode) In this mode PinLayout will call your view's safeAreaInsetsDidChange() method only if the view implements the PinSafeAreaInsetsUpdate protocol. This ensure that PinLayout doesn't interfere with any source code that expect that safeAreaInsetsDidChange() is called only on iOS 11.

       Pin.safeAreaInsetsDidChangeMode = .optIn
       ...
       
       class CustomerView: UIView, PinSafeAreaInsetsUpdate {
          override func safeAreaInsetsDidChange() {
            // This  method will be called on iOS 11, but also on iOS 7/8/9/10 
            // because the view implements the protocol PinSafeAreaInsetsUpdate
            if #available(iOS 11.0, *) {
               super.safeAreaInsetsDidChange()
            }
             ...
         }
      }
    • disable: In this mode PinLayout won't call UIView.safeAreaInsetsDidChange on iOS 8/9/10. Note that this is the default mode on iOS 8.

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.

   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.

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


2. pin.readableMargins

Property:
  • pin.readableMargins: UIEdgeInset:
    PinLayout's UIView.pin.readableMargins property expose UIKit UIView.readableContentGuide as an UIEdgeInsets. This is really useful since UIKit only expose the readableContent area to Auto Layout using UILayoutGuide.
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)

📌 The Examples App contains some examples using pin.readableMargins.


3. pin.layoutmargins

Property:
  • pin.layoutmargins: UIEdgeInset
    PinLayout's UIView.pin.layoutMargins property expose directly the value of UIKit UIView.layoutMargins. The property exists only to be consistent with the other areas: pin.safeArea, pin.readableMargins and pin.layoutmargins. So its usage is not necessary.
Usage example:
	view.pin.left(container.pin.layoutmargins)
	view.pin.left(container.layoutmargins)     // Similar to the previous line

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:

  • wrapContent()
    wrapContent(padding: CGFloat)
    wrapContent(padding: UIEdgeInsets)
    Adjust the view's width and height to wrap all its subviews. The method also adjusts subviews's position to create a tight wrap. It is also possible to specify an optional padding around all subviews.
  • wrapContent(:WrapType)
    wrapContent(:WrapType, padding: CGFloat) wrapContent(:WrapType, padding: UIEdgeInsets)
    Adjust the view's width AND/OR height to wrap all its subviews. Accept a WrapType parameter to define the wrapping type. It is also possible to specify an optional padding around all subviews.

Types:

  • WrapType values:
    • .horizontally: Adjust the view's width and update subviews's horizontal position.
    • .vertically: Adjust only the view's height and update subviews's vertical position.
    • .all: Adjust the view's width AND height and update subviews position. This is the default WrapType parameter value wrapContent() methods.
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:

Source code Result Description
view.pin.wrapContent() Adjust the view's height and width to tight fit its subviews.
view.pin.wrapContent(padding: 10) Adjust the view's height and width and add a padding of 10 pixels around its subviews.
view.pin.wrapContent(.horizontally) Adjust only the view's width.
view.pin.wrapContent(.vertically) 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.

   label.pin.below(of: imageView, aligned: .center).marginTop(4)
   containerView.pin.wrapContent(padding: 10).center()
  • Line 1: Position the label below the imageView aligned on its center with a top margin of 4 pixels.
  • Line 2: Adjust the containerView's size and position its subviews to create a tight wrap around them with a padding of 10 pixels all around. The containerView is also centered inside its parent (superview).

justify() / align()

Methods:

  • justify(_ : HorizontalAlign)
    Justify the view horizontally. This method justifies horizontally a view in situations where the left, right and the width has been set (using either width/minWidth/maxWidth). In this situation, the view may be smaller than the space available between the left and the right edges. A view can be justified left, center, right, start*, end*.

  • align(_ : VerticalAlign)
    Align the view vertically. This method aligns vertically a view in situations where the top, bottom and the height has been set (using either height/minHeight/maxHeight). In this situation, the view may be smaller than the space available between the top and the bottom edges. A view can be aligned top, center or bottom.

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:

   viewA.pin.horizontally().maxWidth(200)

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

   viewA.pin.horizontally().maxWidth(200).justify(.center)

And finally using justify(.right):

   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.

   viewB.pin.after(of: viewA, aligned: .top).right().width(100).justify(.center)

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.

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, ...).

  • pin: Set the position and the size of the non-transformed view. The layout is applied before the transform. This is particularly useful when you want to animate a view using a transform without modifying its layout.

  • pinFrame: Set the position and the size on the transformed view. The layout is applied after the transform.

Examples

The following examples use this view initial size and position:

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 pin result using pinFrame
Rotation transform
  • Using pin the view is layouted and after the rotation transform is applied.
  • Using pinFrame the rotation transform is applied and after the view is layouted.

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.

  • pin: PinLayout will use the untransformed size and the position of the relative view.
  • pinFrame: PinLayout will use the transformed size and the position of the relative view.
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 pin result using pinFrame
Scale transform
  • Using pin the view B is layouted below the untransformed view A (shown in dotted lines).
  • Using pinFrame view B is layouted below the transformed view 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:

  • The newly pinned attributes conflict with other already pinned attributes.
    Example:
    view.pin.left(10).right(10).width(200)
    👉 Layout Conflict: width(200) won't be applied since it conflicts with the following already set properties: left: 0, right: 10.

  • The newly pinned attributes have already been set to another value.
    Example:
    view.pin.width(100).width(200)
    👉 Layout Conflict: width(200) won't be applied since it value has already been set to 100.

  • The view being layout hasn’t been added yet into a superview
    Example:
    view.pin.width(100)
    👉 Layout Warning: width(100) won't be applied, the view must be added as a sub-view before being layouted using this method.

  • A view is used as a reference, either directly or using its anchors or its edges, but hasn’t been added yet to a superview.
    Example:
    view.pin.left(of: view2)
    👉 Layout Warning: left(of: view2) won't be applied, the view must be added as a sub-view before being used as a reference.

  • The width and the height must be positive values.
    Example:
    view.pin.width(-100)
    👉 Layout Warning: The width (-100) must be greater or equal to 0.

  • justify(.left|.center|.right) is used without having set the left and the right coordinates.
    Example:
    view.pin.left().width(250).justify(.center)
    👉 PinLayout Warning: justify(center) won't be applied, the left and right coordinates must be set to justify the view.

  • Layout must be executed from the Main thread.
    👉 PinLayout Warning: Layout must be executed from the Main Thread!

  • Layout must be executed from the Main thread.

  • ...

Enabling/Disabling warnings

Property:
  • Pin.logWarnings: Boolean
    This property specifies if PinLayout's warnings are displayed in the console. In Debug (#if DEBUG) the default value is true, else its false. The value can be modified at runtime.

Enabling/Disabling warnings individually

Few individual warnings can also be enabled/disabled individually:

  • Pin.activeWarnings.noSpaceAvailableBetweenViews: Boolean
    If true, a warning is displayed if there is no space available between views specified in a call to horizontallyBetween(...) or verticallyBetween(...)
  • Pin.activeWarnings. aspectRatioImageNotSet: Boolean
    If true, a warning is displayed if 'aspectRatio()' is called on a UIImageView without a valid UIImage.

PinLayout style guide

  • You should always specifies methods in the same order, it makes layout lines easier to understand. Here is our preferred ordering:
    view.pin.[EDGE|ANCHOR|RELATIVE].[WIDTH|HEIGHT|SIZE].[pinEdges()].[MARGINS].[sizeToFit()]

    This order reflect the logic inside PinLayout. pinEdges() is applied before margins and margins are applied before sizeToFit().

     view.pin.top().left(10%).margin(10, 12, 10, 12)
     view.pin.left().width(100%).pinEdges().marginHorizontal(12)
     view.pin.horizontally().margin(0, 12).sizeToFit(.width)
     view.pin.width(100).height(100%)
  • You should specify edges always in the same order, this is our proposed order:
    TOP, BOTTOM, LEFT, RIGHT

     view.pin.top().bottom().left(10%).right(10%)
  • If the layout line is too long, you can split into multiple lines:

     textLabel.pin.below(of: titleLabel)
        .before(of: statusIcon).after(of: accessoryView)
        .above(of: button).marginHorizontal(10)

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


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.


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.

Cell A:

  • A1 is left aligned with a width of 50px
  • A2 fills the remaining space
   a1.pin.vertically().left().width(50)
   a2.pin.after(of: a1, aligned: .top).bottomRight().marginLeft(10)

Cell B:

  • B2 is right aligned with a fixed width of 50px
  • B1 fills the remaining space
   b2.pin.vertically().right().width(50)
   b1.pin.before(of: b2, aligned: .top).bottom().left().marginRight(10)

Cell C:

  • C2 is centered with a fixed width of 50px
  • C1 fills the remaining left space
  • C3 fills the remaining right space
   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 takes 25% of its container width
  • D2 takes 50% of its container width
  • D3 fills the remaining space
   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)

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.


Examples App

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

See the Example App section to get more information

Included examples:


macOS Support

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

  • PinLayout supports only views that have a parent (superview) using a flipped coordinate system, i.e. views for which the computed property var isFlipped: Bool returns true. In a flipped coordinate system, the origin is in the upper-left corner of the view and y-values extend downward. UIKit use this coordinate system. In a non-flipped coordinate system (default mode), the origin is in the lower-left corner of the view and positive y-values extend upward. See Apple's documentation for more information about NSView.isFlipped. The support of non-flipped coordinate system will be added soon.

  • sizeToFit(:FitType) is supported only for instances that inherits from NSControl. Support for sizeToFit(:FitType) can be added to your custom NSView subclasses, just make those views conform to the SizeCalculable protocol and implement the required sizeThatFits(:CGSize) function.

  • NSView.pin.safeArea and property is not available, AppKit doesn't have an UIView.safeAreaInsets equivalent.

  • NSView.pin.readableMargins property is not available, AppKit doesn't have an UIView.readableContentGuide equivalent.

  • aspectRatio() with no parameters.


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)

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


PinLayout using Objective-C

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

See here for more details


FAQ

  • Q: When the device rotation change, the layout is not updated.
    R: PinLayout doesn't use auto layout constraints, it is a framework that manually layout views. For that reason you need to update the layout inside either UIView.layoutSubviews() or UIViewController.viewDidLayoutSubviews() to handle container size's changes, including device rotation. You'll also need to handle UITraitCollection changes for app's that support multitasking.

  • Q: How to handle new iOS 11 UIView.safeAreaInsets and the iPhone X .
    R: iOS 11 has introduced UIView.safeAreaInsets to particularly support the iPhone X landscape mode. In this mode UIView.safeAreaInsets has a left and right insets. The easiest way the handle this situation with PinLayout is to add a contentView that will contains all your view's child, and simply adjust this contentView view to match the safeAreaInsets or PinLayout's UIView.pin.safeArea.

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.

  • Q: How can we adjust a UIView container to match all its children?
    R: The proposed solution is used by the Form example for its rounded corner background. Suppose you want to adjust a container height to match all its child (subviews).

    1. First set the container width and its position:
      containerView.pin.topCenter().width(100%).marginTop(10)
    2. Layout all its children.
    3. Finally, set the container height to match its last child Y position:
      containerView.pin.height(child6.frame.maxY + 10)
  • Q: How to apply percentage from a CGFloat, a Float or an Int value?
    R: Many PinLayout's method has a parameter of type Percent. You can easily specify this type of parameter simply by adding the % operator to your value (eg: view.pin.left(10%).width(50%). It is similar if you have a value of type CGFloat, Float or Int, simply adds the % operator:

     let percentageValue: CGFloat = 50
     view.pin.width(percentageValue%)

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:

  • HTML's CSS: Management of margins in absolute positioning and bottom/right position coordinates.
  • MCUIViewLayout: Nice absolute and relative positioning.
  • Qt: Anchors and edges management.
  • SnapKit: Clean interface for anchors.

History

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

Recent breaking change

  • fitSize() has been removed after being deprecated for 10 months. sizeToFit(...) should now be used instead. See Adjusting size. (2018-08-21)

License

MIT License

Comments
  • Use anchor then could not use margins

    Use anchor then could not use margins

    Hello. I have a question that if I have a button and pin its center to topRight of view1, but I wish to have a top margin and it seems not work. Code is below:

    button.pin.center(to: view1.anchor.topRight).marginTop(10)

    The top margin won't apply. Maybe there is another way?

    bug question 
    opened by wjling 12
  • Wired layout behavior maybe caused by release 1.5.2

    Wired layout behavior maybe caused by release 1.5.2

    If use PinLayout, scrolling tableView will be relayout repeatedly and acts wired. Using frame will be fine. I have an issue demo here: https://github.com/wjling/PinTestDemo1

    Since update to 1.5.6,I found many issues in my app except I mentioned above😭, not only transform. I think these layout issues have something to do with 1.5.2 or 1.5.3. Maybe PinLayout changes its mechanism about seting frame or bounds

    question 
    opened by wjling 8
  • Can not try in Xcode Playground

    Can not try in Xcode Playground

    I've tried PinLayout in Xcode Playground. But, It does not work. You know, PinLayout run layout on deinit themselves. But, it seems like deinit of PinLayout is different from normal application.

    I tried this project. https://cl.ly/3Q0H1h2e2D2H

    image 2017-11-13 at 22 38 19 public

    bug help wanted 
    opened by muukii 8
  • Crash: PinLayout.swift - Line 1236

    Crash: PinLayout.swift - Line 1236

    Firebase crashlytics is reporting that our app is crashing after PinLayout tries to call referenceSuperview and this method calls viewDescription. It seems that the object referenceView which should be created from anchor.view in the method computeCoordinatesForAnchors in the line 384 of PinLayout+Coordinates is nil. And this causes referenceSuperview to fail to create a reference to the superview from referenceView.superview. Because of this when warnWontBeApplied is called in line 1236 of PinLayout crashes when it tries to call viewDescription(referenceView)

    This is the stacktrace from crashlytics:

    Crashed: com.apple.main-thread
    0  libswiftCore.dylib             0x3da990 swift_getObjectType + 40
    1  PinLayout                      0x2557c PinLayout.viewDescription(_:) + 4375287164 (<compiler-generated>:4375287164)
    2  PinLayout                      0xb684 PinLayout.referenceSuperview(_:_:) + 1236 (PinLayout.swift:1236)
    3  PinLayout                      0x101b4 closure #1 in PinLayout.computeCoordinates(forAnchors:_:) + 384 (PinLayout+Coordinates.swift:384)
    4  PinLayout                      0xfdb8 PinLayout.computeCoordinates(forAnchors:_:) + 4375199160 (<compiler-generated>:4375199160)
    5  PinLayout                      0x1987c PinLayout.below(of:aligned:context:) + 170 (PinLayout+Relative.swift:170)
    6  PinLayout                      0x19e30 PinLayout.above(of:aligned:) + 4375240240
    7  NYP                            0x2b9fd4 NYPVerticalContainerFrameView.layout() + 92 (NYPVerticalContainerFrameView.swift:92)
    8  NYP                            0x2ba9a4 protocol witness for FrameViewProtocol.layout() in conformance NYPVerticalContainerFrameView + 4334070180 (<compiler-generated>:4334070180)
    9  NYP                            0x6f7e48 FrameViewProtocol<>.heightThatFits(_:) + 43 (FrameViewType.swift:43)
    10 NYP                            0x26b3ac NYPVerticalContainerFrame.height(forWidth:) + 91 (NYPVerticalContainerFrame.swift:91)
    11 NYP                            0x654360 ContainerCollectionViewLayout.attributesFor(frame:currentXOffset:currentYOffset:indexPath:inRow:) + 47 (Container+LayoutHelpers.swift:47)
    12 NYP                            0x65b068 calculateAttributes #1 (for:) in attemptToLayout #1 (frame:atIndex:) in ContainerCollectionViewLayout.prepare() + 447 (ContainerCollectionViewLayout.swift:447)
    13 NYP                            0x65a1d4 attemptToLayout #1 (frame:atIndex:) in ContainerCollectionViewLayout.prepare() + 472 (ContainerCollectionViewLayout.swift:472)
    14 NYP                            0x655ec0 ContainerCollectionViewLayout.prepare() + 498 (ContainerCollectionViewLayout.swift:498)
    15 NYP                            0x65bee8 @objc ContainerCollectionViewLayout.prepare() + 4337876712 (<compiler-generated>:4337876712)
    16 UIKitCore                      0x174154 -[UICollectionViewData _prepareToLoadData] + 200
    17 UIKitCore                      0x176b4c -[UICollectionViewData validateLayoutInRect:] + 96
    18 UIKitCore                      0x179be8 -[UICollectionView layoutSubviews] + 220
    19 UIKitCore                      0x18c17c -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2592
    20 QuartzCore                     0x407fc CA::Layer::layout_if_needed(CA::Transaction*) + 532
    21 QuartzCore                     0x32c60 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 136
    22 QuartzCore                     0x475b4 CA::Context::commit_transaction(CA::Transaction*, double, double*) + 452
    23 QuartzCore                     0x504a8 CA::Transaction::commit() + 704
    24 QuartzCore                     0x323a0 CA::Transaction::flush_as_runloop_observer(bool) + 88
    25 UIKitCore                      0x53e6e0 _UIApplicationFlushCATransaction + 72
    26 UIKitCore                      0x7d8d5c _UIUpdateSequenceRun + 84
    27 UIKitCore                      0xe5fedc schedulerStepScheduledMainSection + 144
    28 UIKitCore                      0xe5f6a4 runloopSourceCallback + 92
    29 CoreFoundation                 0xbb414 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
    30 CoreFoundation                 0xcc1a0 __CFRunLoopDoSource0 + 208
    31 CoreFoundation                 0x5694 __CFRunLoopDoSources0 + 268
    32 CoreFoundation                 0xb05c __CFRunLoopRun + 828
    33 CoreFoundation                 0x1ebc8 CFRunLoopRunSpecific + 600
    34 GraphicsServices               0x1374 GSEventRunModal + 164
    35 UIKitCore                      0x514b58 -[UIApplication _run] + 1100
    36 UIKitCore                      0x296090 UIApplicationMain + 364
    37 NYP                            0x66f8 main + 24 (NYPMediaCoder.swift:24)
    38 ???                            0x10392dda4 (Missing)
    

    I'm trying to figure out what is causing the issue.

    We are doing something like this:

    var margin = viewModel.margins
    var height: CGFloat = 0.0
    
    for (n, uiView) in frameView.enumerated() { 
       if n == 0 {
            uiView.pin.top(margin).horizontally(margin).height(height)
       } else {                  
            let previousView = frameView[n - 1]
       if n == frameView.count - 1 {
            uiView.pin.below(of: previousView).bottom(margin).horizontally(margin).height(height)
       } else {
            uiView.pin.below(of: previousView).horizontally(margin).height(height)
               }
            }
         }
    

    According to the data from the stacktrace: NYPVerticalContainerFrameView.layout() + 92 Is the line if n == frameView.count - 1 { ... but I think the crash is being caused by line 93 which is:

    uiView.pin.below(of: previousView).bottom(margin).horizontally(margin).height(height)

    Any insights into this?

    opened by orojasnypost 7
  • invalid parameter not satisfying: !cgsizeequaltosize(size, cgsizezero)

    invalid parameter not satisfying: !cgsizeequaltosize(size, cgsizezero)

    I use UICollectionViewCompositionalLayout to layout my collectionView. I have a custom cell A and a custom view B. Cell A's contentView adds B as a subview. Both cell A and view B has autoSizeThatFits in method sizeThatFits. But I get an exception when I enter the viewController said invalid parameter not satisfying: !cgsizeequaltosize(size, cgsizezero). If I remove autoSizeThatFits code and it runs without exception. I can't figure out where is wrong.

    opened by wjling 7
  • vCenter() incorrectly alters the anchor point.

    vCenter() incorrectly alters the anchor point.

    Screen Shot 2019-03-20 at 11 18 20 PM Screen Shot 2019-03-20 at 11 18 29 PM

    Box 1 is Red Box 2 is Blue Box 4 is Pink.

    As you can see, the center anchor point of box4 should be on the bottom-right of box2, but its way off. I tested it without vCenter, and it worked perfectly.

    help wanted 
    opened by MaruthiBasava 7
  • Top value swifts based on height

    Top value swifts based on height

    So I have a simple text defined as follows:

    self.descriptionCustom = UILabel().then {
                    $0.text = descriptionLabel.text
                    $0.textColor = .white
                    $0.numberOfLines = 5
                    $0.font = UIFont(name: "Rubik-Regular", size: 14.0)
                    $0.baselineAdjustment = .none
                    $0.textAlignment = .left
                    
                    self.scrollView.addSubview($0)
                }
                
       descriptionCustom.pin.left(8).width(UIScreen.main.bounds.size.width-50).height(90%).top(0)
    

    And this UILabel text changes dynamically, the problem is that the top of the UILabel changes position (either higher or lower) depending on the height of the text, while I want it Pinned (no pun intented) on the same spot and just to adjust its height vertically.

    Is there a property I should be using or some special syntax?

    Thanks!

    question 
    opened by netgfx 7
  • Consider adding the offset parameter to Anchor.

    Consider adding the offset parameter to Anchor.

    For example:

    from

    view.pin.bottom(10).left(10)
    

    to

    view.pin.bottomLeft(10)
    

    I have another question, for example, I have a button that has an image, obviously it has an intrinsic size, but if I only specify its origin when laying out, it won't show because the size is 0. My current approach is to manually call sizeToFit() to set its size when it is initialized, or to hardcode its size during layout. pin.sizeToFit(.xxx) does not correctly set its size. These approach is obviously not elegant enough, I wonder if I can automatically use its intrinsic size like AutoLayout.

    enhancement 
    opened by VincentSit 7
  • Can Aspect Ratio be used with AVPlayer video player?

    Can Aspect Ratio be used with AVPlayer video player?

    Looking into how can I resize the view of the AVPlayerViewController to detect and resize based on video aspect ratio and my own constraints (height no larger than 50% of view or horizontal align)

    So the question is can aspectRatio propety be used with video player or only images?

    And if not how can I change dynamically the height constraints after I detect each video width/height?

    Thanks in advance!

    question 
    opened by netgfx 7
  • Add automatic sizeThatFits computation for views

    Add automatic sizeThatFits computation for views

    Disclaimer

    This is a preliminary PR for discussion purpose. The implemented functionality has barely been tested and is not guaranteed to be bug free.

    Motivation

    Implementing sizeThatFits(_ size: CGSize) as part of the manual layout process has always been cumbersome. You always end up writing the same code twice, a first time for the layout and the second time for sizing. Using PinLayout to compute the resulting size like showcased in the AdjustToContainer exemple is not recommended either because the view coordinates are modified during sizing. The sizeThatFits method documentation state clearly:

    This method does not resize the receiver.

    Proposal

    Build an autosizing mechanism on top of PinLayout without modifying the view's coordinates in the process.

    • This PR add the AutoSizeCalculable interface that defines autosizing related functions and properties.
    • An internal flag (Pin.autoSizingInProgress) is also needed for the layout system to know if it should compute an additional rect including the margins.
    • Implementing sizeThatFits(_ size: CGSize) using automatic sizing requires the following:
      • Layout code is preferably located in a separate function than layoutSubviews() to be sure things like setting the content size on a scroll view are not executed during the sizing process. In the provided exemple, that function would be layout().
      • The sizeThatFits implementation is as simple as calling return autoSizeThatFits(size) { layout() } and even takes into account the outer margins (ie: the bottom margin applied to the last view on y axis)

    Limitations

    • Sadly I don't see any way of adding this capability directly on Layoutable because of the need to define stored properties.
    • All layout related code must be done using PinLayout only, otherwise the views that uses another layout system would be ignored in the resulting computed size.

    Discussion

    I would very much like feedback on this PR discussing the concept and exposing the potential flaws if any. I think that if we can get this thing to work properly in every situation it would be a great addition to PinLayout.

    opened by antoinelamy 6
  • Decreasing resizing not animated

    Decreasing resizing not animated

    Hi. When I'm animating view size change, the animation is executed only when view is increasing in size, but when the view is decreased in size, animation is not executed and view is resized right away.

    Example: Lets have a view: view.pin.width(50).height(50).left().top() Lets increase view size: UIView.animate(withDuration: 0.16) { view.pin.width(100).height(100) } .. OK, view was resized with animation Lets decrease view size: UIView.animate(withDuration: 0.16) { view.pin.width(30).height(30) } .. NOT OK, no animation happens here, view is just resized to 30x30.

    Is this a bug? Or do I need to do something more to get decreasing animation?

    help wanted 
    opened by rr-dw 6
  • Crash: PinLayout.swift - Line 1236

    Crash: PinLayout.swift - Line 1236

    I'm afraid that the issue is still happening.

    Firebase crashlytics is reporting that our app is crashing after PinLayout tries to call referenceSuperview and this method calls viewDescription. It seems that the object referenceView which should be created from anchor.view in the method computeCoordinatesForAnchors in the line 384 of PinLayout+Coordinates is nil. And this causes referenceSuperview to fail to create a reference to the superview from referenceView.superview. Because of this when warnWontBeApplied is called in line 1236 of PinLayout crashes when it tries to call viewDescription(referenceView)

    This is what we've tried:

    All views and layout updating is done in the main thread. Pin.logWarnings = true is nowhere in our code I tried validating that the superView of the previous view isn't nil before pinning it. The fix was released but the issue persists in Crashlytics. (I'm not able to reproduce it locally).

    This is the stacktrace from crashlytics:

    Crashed: com.apple.main-thread 0 libswiftCore.dylib 0x41dd74 swift_getObjectType + 40 1 PinLayout 0x24910 PinLayout.viewDescription(:) + 100 (:100) 2 PinLayout 0xa258 PinLayout.referenceSuperview(::) + 1236 (PinLayout.swift:1236) 3 PinLayout 0xee38 closure #1 in PinLayout.computeCoordinates(forAnchors::) + 384 (PinLayout+Coordinates.swift:384) 4 PinLayout 0xea30 PinLayout.computeCoordinates(forAnchors::) + 196 (:196) 5 PinLayout 0x18900 PinLayout.below(of:aligned:context:) + 170 (PinLayout+Relative.swift:170) 6 PinLayout 0x18e68 PinLayout.above(of:aligned:) + 244 7 NYP 0x2e40ec NYPVerticalContainerFrameView.layout() + 95 (NYPVerticalContainerFrameView.swift:95) 8 NYP 0x2e4d74 protocol witness for FrameViewProtocol.layout() in conformance NYPVerticalContainerFrameView + 20 (:20) 9 NYP 0x7369a8 FrameViewProtocol<>.heightThatFits(:) + 43 (FrameViewType.swift:43) 10 NYP 0x2929ec NYPVerticalContainerFrame.height(forWidth:) + 91 (NYPVerticalContainerFrame.swift:91) 11 NYP 0x68ea1c ContainerCollectionViewLayout.attributesFor(frame:currentXOffset:currentYOffset:indexPath:inRow:) + 47 (Container+LayoutHelpers.swift:47) 12 NYP 0x69528c calculateAttributes #1 (for:) in attemptToLayout #1 (frame:atIndex:) in ContainerCollectionViewLayout.prepare() + 447 (ContainerCollectionViewLayout.swift:447) 13 NYP 0x69449c attemptToLayout #1 (frame:atIndex:) in ContainerCollectionViewLayout.prepare() + 472 (ContainerCollectionViewLayout.swift:472) 14 NYP 0x690690 ContainerCollectionViewLayout.prepare() + 498 (ContainerCollectionViewLayout.swift:498) 15 NYP 0x695db0 @objc ContainerCollectionViewLayout.prepare() + 28 (:28) 16 UIKitCore 0x76828 -[UICollectionViewData _prepareToLoadData] + 192 17 UIKitCore 0x444020 -[UICollectionViewData validateLayoutInRect:] + 96 18 UIKitCore 0x29ac8 -[UICollectionView layoutSubviews] + 220 19 UIKitCore 0x4cec -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1980 20 QuartzCore 0xa4e8 CA::Layer::layout_if_needed(CA::Transaction*) + 500 21 UIKitCore 0xd2ae8 -[UIView(Hierarchy) layoutBelowIfNeeded] + 292 22 UIKitCore 0x781a98 __66-[UINavigationController _updateBottomBarsForNavigationTransition]_block_invoke + 152 23 UIKitCore 0x316d0 +[UIView(Animation) performWithoutAnimation:] + 76 24 UIKitCore 0x781800 -[UINavigationController _updateBottomBarsForNavigationTransition] + 240 25 UIKitCore 0x277ea4 -[UINavigationController _navigationBarWillBeginCoordinatedTransitionAnimations:] + 224 26 UIKitCore 0x277da0 -[UINavigationBar _sendNavigationBarAnimateTransition] + 72 27 UIKitCore 0x27546c __96-[_UINavigationBarVisualProviderModernIOS _performAnimationWithTransitionCompletion:transition:]_block_invoke_2 + 40 28 UIKitCore 0xd0af0 +[UIView _setupAnimationWithDuration:delay:view:options:factory:animations:start:animationStateGenerator:completion:] + 512 29 UIKitCore 0x1012f8 +[UIView(UIViewAnimationWithBlocks) _animateWithDuration:delay:options:factory:animations:completion:] + 52 30 UIKitCore 0xfe470 -[_UINavigationBarVisualProviderModernIOS _performAnimationWithTransitionCompletion:transition:] + 2044 31 UIKitCore 0x2ed4d0 -[UINavigationBar _pushNavigationItem:transitionAssistant:] + 464 32 UIKitCore 0x2ed280 -[UINavigationBar _pushNavigationItemUsingCurrentTransition:] + 144 33 UIKitCore 0x2ebc04 -[UINavigationBar pushNavigationItem:animated:] + 164 34 UIKitCore 0x2ebb40 -[UINavigationBar _performUpdatesIgnoringLock:] + 68 35 UIKitCore 0x2ebae0 -[UINavigationBar _pushNavigationItem:transition:] + 140 36 UIKitCore 0x1b7618 __71-[UINavigationController pushViewController:transition:forceImmediate:]_block_invoke + 292 37 UIKitCore 0x77ff08 __71-[UINavigationController pushViewController:transition:forceImmediate:]_block_invoke_3 + 88 38 UIKitCore 0x105270 __98-[UINavigationController _shouldSkipHostedRefreshControlUpdateSchedulingDeferredUpdateIfNecessary]_block_invoke + 40 39 UIKitCore 0x1a7bb4 -[UINavigationController _startDeferredTransitionIfNeeded:] + 880 40 UIKitCore 0x1a70f4 -[UINavigationController __viewWillLayoutSubviews] + 96 41 UIKitCore 0x1a7058 -[UILayoutContainerView layoutSubviews] + 172 42 UIKitCore 0x4cec -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1980 43 QuartzCore 0xa4e8 CA::Layer::layout_if_needed(CA::Transaction*) + 500 44 QuartzCore 0x1db9c CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 148 45 QuartzCore 0x2f098 CA::Context::commit_transaction(CA::Transaction*, double, double*) + 456 46 QuartzCore 0x663c4 CA::Transaction::commit() + 652 47 QuartzCore 0x4d408 CA::Transaction::flush_as_runloop_observer(bool) + 88 48 UIKitCore 0x504fb0 _UIApplicationFlushCATransaction + 52 49 UIKitCore 0x651678 _UIUpdateSequenceRun + 84 50 UIKitCore 0xc90904 schedulerStepScheduledMainSection + 172 51 UIKitCore 0xc8fad0 runloopSourceCallback + 92 52 CoreFoundation 0xd622c CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 28 53 CoreFoundation 0xe2614 __CFRunLoopDoSource0 + 176 54 CoreFoundation 0x6651c __CFRunLoopDoSources0 + 244 55 CoreFoundation 0x7beb8 __CFRunLoopRun + 836 56 CoreFoundation 0x811e4 CFRunLoopRunSpecific + 612 57 GraphicsServices 0x1368 GSEventRunModal + 164 58 UIKitCore 0x3a2d88 -[UIApplication _run] + 888 59 UIKitCore 0x3a29ec UIApplicationMain + 340 60 NYP 0x97f8 main + 24 (AppDelegate.swift:24) 61 ??? 0x1b5791948 (Missing)

    opened by orojasnypost 0
  • Unable to build with Carthage

    Unable to build with Carthage

    With Carthage 0.38 when running carthage update --use-xcframeworks with a basic Cartfile like this github "layoutBox/PinLayout" im getting this

    ❯ carthage update --use-xcframeworks
    *** Fetching PinLayout
    *** Checking out PinLayout at "1.10.3"
    *** xcodebuild output can be found in /var/folders/kp/c82dqjjx7kq7wm13b_y776tw0000gq/T/carthage-xcodebuild.zqbOht.log
    A shell task (/usr/bin/xcrun xcodebuild -project /Users/toke.refstrup/git/PInLayout/Carthage/Checkouts/PinLayout/TestProjects/swift-package-manager/ios/PinLayout-Carthage-iOS.xcodeproj CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY= CARTHAGE=YES -list) failed with exit code 74:
    2022-12-06 10:28:01.011 xcodebuild[10652:12572803] Writing error result bundle to /var/folders/kp/c82dqjjx7kq7wm13b_y776tw0000gq/T/ResultBundle_2022-06-12_10-28-0001.xcresult
    xcodebuild: error: Unable to read project 'PinLayout-Carthage-iOS.xcodeproj' from folder '/Users/toke.refstrup/git/PInLayout/Carthage/Checkouts/PinLayout/TestProjects/swift-package-manager/ios'.
    	Reason: Project /Users/toke.refstrup/git/PInLayout/Carthage/Checkouts/PinLayout/TestProjects/swift-package-manager/ios/PinLayout-Carthage-iOS.xcodeproj cannot be opened because it is missing its project.pbxproj file.
    
    

    Can anyone help me with resolving this issue?

    opened by icedice 0
  • Interview partners for research about communication in GitHub projects wanted

    Interview partners for research about communication in GitHub projects wanted

    Hi. My name is Verena Ebert, and I am a PhD student at the University of Stuttgart in Germany.
    A few months ago, I have examined 90 GitHub projects to see what communication channels they use and how they write about them in the written documents, for example README or wiki. If you are interested in the previous results, you can find them here: https://arxiv.org/abs/2205.01440 Your project was one of these 90 projects and, therefore, I am interested in further details about your communication setup.

    To gather more data about your communication setup, I kindly ask one of the maintainers to do an interview with me. The interview will be about 30-35 minutes long and via Skype, WebEx or any other provider you prefer. The interviews should be done in November 2022, if possible.

    In this interview, I would like to ask some questions about the reasons behind the channels, to understand the thoughts of the maintainers in addition to the written information.

    The long goal of my PhD is to understand how communication works in GitHub projects and how a good set of communication channels and information for other maintainers and developers looks like. One possible outcome is a COMMUNICATION.md with instructions and tips about which channels could be useful and how these channels should be introduced to other projects participants. Of course, if you are interested, I will keep you up to date about any further results in my research.

    If you are interested in doing an interview, please respond here or contact me via email ([email protected]). We will then make an appointment for the interview at a time and date that suits you.

    If you agree, I would like to record the interview and transcribe the spoken texts into anonymized written texts. In this case, I will send you the transcript for corrections afterwards. Only if you agree, the transcripts or parts of it would be part of a publication.

    opened by verenya 0
  • Improve Views Concatenation Methods

    Improve Views Concatenation Methods

    Description

    This PR aims to improve to improve the behaviour of methods like below(of ... or above(of ... allowing to pin the view to the top/bottom of its superview.

    Motivation and Context

    Using PinLayout frequently to chain views with the following APIs below(of ..., above(of ..., or after(of ... combined with visible( ... ) method, it's easy and fast but we often find ourself facing the same problem: the view on top must always be visible. In some cases, however, it would be useful and convenient to overcome this trivial limitation by anchoring the first visible view to the superview’s edge.

    The solution adopted by me and also my colleagues is to use an if else statement inside the layout method that check the visibility of the previous/next relative view:

    func performLayout() {
        firstView.pin
          .top()
          .size(100)
          .hCenter()
          .marginTop(10)
        
        if firstView.isHidden {
          secondView.pin
            .top()
        } else {
          secondView.pin
            .below(of: firstView)
        }
        ...
    }
    

    With this pull request I would like to discuss a possible solution implemented as a set of methods that after checking the visibility state of his relative views, instead of causing the layout to fail, pin the view to the edge of the superview (top/bottom/left/right depending on the method used).

    /// Example of a view that is attached below of the last visible `relativeViews`
    /// or to `top()` if all relative views are hidden.
    @discardableResult
    public func topIfNotBelow(of relativeViews: [PinView], aligned: HorizontalAlign = .none) -> PinLayout {
      func context() -> String { return relativeContext("below", relativeViews, aligned) }
      return topIfNotBelow(of: relativeViews, aligned: aligned, context: context)
    }
    
    fileprivate func topIfNotBelow(of relativeViews: [PinView], aligned: HorizontalAlign, context: Context ) -> PinLayout {
      guard layoutSuperview(context) != nil else { return self }
      guard relativeViews.count > 0 else {
        setTop(0, context)
        return self
      }
      
      let anchors: [Anchor]
      switch aligned {
      case .left:   anchors = relativeViews.map({ $0.anchor.bottomLeft })
      case .center: anchors = relativeViews.map({ $0.anchor.bottomCenter })
      case .right:  anchors = relativeViews.map({ $0.anchor.bottomRight })
      case .start:  anchors = isLTR() ? relativeViews.map({ $0.anchor.bottomLeft }) : relativeViews.map({ $0.anchor.bottomRight })
      case .end:    anchors = isLTR() ? relativeViews.map({ $0.anchor.bottomRight }) : relativeViews.map({ $0.anchor.bottomLeft })
      case .none:   anchors = relativeViews.map({ $0.anchor.bottomLeft })
      }
      
      if let coordinates = computeCoordinates(forAnchors: anchors, context) {
        setTop(getBottomMostCoordinate(list: coordinates), context)
        applyHorizontalAlignment(aligned, coordinates: coordinates, context: context)
      }
      return self
    }
    

    Using this method as follow:

    func performLayout() {
      redView.pin
        .top()
        .size(100)
        .hCenter()
        .marginTop(10)
      
      blueView.pin
        .topIfNotBelow(of: visible([redView]))
        .size(100)
        .hCenter()
        .marginTop(12)
      
      greenView.pin
        .topIfNotBelow(of: visible([redView, blueView]))
        .size(100)
        .hCenter()
        .marginTop(12)
      
      ...
    }
    

    I’ll leave here a short video to show animations as well.

    https://user-images.githubusercontent.com/16403933/196270311-1e2a0587-b916-4f21-9967-539953ae2c00.mp4

    More Thoughts

    It could also be thought of a version of this proposal that improves the names of the methods added by me. Existing functions (such as below(of ...)) could be improved by adding a parameter that describes where the view should be pin in case there are no relative views visible (as default it will maintain the default behaviour):

    enum ParentVerticaAnchor {
      case none
      
      case top
      
      case bottom
      
      case center
    }
    
    fileprivate func below(of relativeViews: [PinView], aligned: HorizontalAlign, ifNeeded anchor: ParentVerticaAnchor = .none, context: Context) -> PinLayout {
      guard layoutSuperview(context) != nil else { return self }
      
      switch anchor {
      case .none:
        guard relativeViews.count > 0 else {
          warnWontBeApplied("At least one view must be visible (i.e. UIView.isHidden != true) ", context)
          return self
        }
        
      case .top:
        guard relativeViews.count > 0 else {
          setTop(0, context)
          return self
        }
        
      case .bottom:
        guard relativeViews.count > 0 else {
          setBottom(0, context)
          return self
        }
        
      case .center:
        guard relativeViews.count > 0 else {
          setVerticalCenter(0, context)
          return self
        }
      }
      
      let anchors: [Anchor]
      switch aligned {
        case .left:   anchors = relativeViews.map({ $0.anchor.bottomLeft })
        case .center: anchors = relativeViews.map({ $0.anchor.bottomCenter })
        case .right:  anchors = relativeViews.map({ $0.anchor.bottomRight })
        case .start:  anchors = isLTR() ? relativeViews.map({ $0.anchor.bottomLeft }) : relativeViews.map({ $0.anchor.bottomRight })
        case .end:    anchors = isLTR() ? relativeViews.map({ $0.anchor.bottomRight }) : relativeViews.map({ $0.anchor.bottomLeft })
        case .none:   anchors = relativeViews.map({ $0.anchor.bottomLeft })
      }
      
      if let coordinates = computeCoordinates(forAnchors: anchors, context) {
        setTop(getBottomMostCoordinate(list: coordinates), context)
        applyHorizontalAlignment(aligned, coordinates: coordinates, context: context)
      }
      return self
    }
    
    opened by pasp94 3
  • How do I auto-size the container of image view and label?

    How do I auto-size the container of image view and label?

    I wanna a view containing an imageView and a label. But I don't know how to auto size the container. Because the number of lines with the label are undecided in the different devices. Please help me.

            self.imageView.pin.width(48).height(48).vCenter()
            self.label.pin.below(of: self.imageView).left(20).right(20).sizeToFit(.width)
            self.containerView.pin.wrapContent().center()
    
    question 
    opened by KhazanGu 5
Releases(1.10.3)
  • 1.10.3(Jun 7, 2022)

  • 1.10.2(Feb 2, 2022)

  • 1.10.1(Feb 1, 2022)

  • 1.10.0(May 18, 2021)

    New Objective-C interface

    Instead of using verbose Objective-C with all brackets ([ ]):

    [[[[[[logo.pinObjc top] left] width:100] aspectRatio] marginWithTop:topLayoutGuide + 10 horizontal:10 bottom:10] layout];
    

    It now use function chaining:

    logo.pinObjc.topInsets(safeArea).leftInsets(safeArea).width(100).aspectRatio().margin(margin).layout();
    

    Added by protosse in Pull Request #229

    Source code(tar.gz)
    Source code(zip)
  • 1.9.4(May 17, 2021)

  • 1.9.3(Dec 17, 2020)

  • 1.9.0(Jun 30, 2020)

    Add Automatic Sizing feature

    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.

    See https://github.com/layoutBox/PinLayout#automatic_sizing for more documentation. * Added by Antoine Lamy in Pull Request #216

    Source code(tar.gz)
    Source code(zip)
  • 1.8.13(Oct 31, 2019)

  • 1.8.12(Oct 7, 2019)

  • 1.8.11(Oct 3, 2019)

  • 1.8.10(Sep 16, 2019)

    • Use UIView.effectiveUserInterfaceLayoutDirection to detect RTL on iOS 10 and above. This is recommended approach to detect layout direction taking into account view's semantic content attribute, trait environment and UIApplication layout direction.
    • Update Travis to Xcode 11.
    Source code(tar.gz)
    Source code(zip)
  • 1.8.9(Aug 15, 2019)

  • 1.8.8(Jun 25, 2019)

  • 1.8.7(Mar 2, 2019)

    • wrapContent
    • wrapContentWithPadding:(CGFloat)
    • wrapContentWithInsets:(PEdgeInsets)
    • wrapContentWithType:(WrapType)
    • wrapContentWithType:(WrapType) padding:(CGFloat)
    • wrapContentWithType:(WrapType) insets:(PEdgeInsets)
    Source code(tar.gz)
    Source code(zip)
  • 1.8.6(Sep 29, 2018)

  • 1.8.5(Sep 27, 2018)

    Remove sizeToFit() from SizeCalculable protocol. This change ensure that PinLayout pin.sizeToFit() method behave correctly. As per the iOS documentation, we should not directly override sizeToFit() but rather always only implement sizeThatFits(_:) for auto-sizing needs. This update aim to remove the sizeToFit() requirement in the SizeCalculable protocol.

    Source code(tar.gz)
    Source code(zip)
  • 1.8.4(Sep 27, 2018)

  • 1.8.3(Aug 28, 2018)

    Add methods to layout a view between two other views

    Add methods to position a view between two other views, either horizontally or vertically.

    New Methods:

    • horizontallyBetween(:UIView, and: UIView)
      Position the view between the two specified views horizontally. The method layout the view's left and right edges. The order of the reference views is irrelevant. Note that the layout will be applied only if there is horizontal space between the specified views.

    • horizontallyBetween(:UIView, and: UIView, aligned: VerticalAlign)
      Position the view between the two specified views horizontally and aligned it using the specified VerticalAlign. The view will be aligned related to the first specified reference view. Note that the layout will be applied only if there is horizontal space between the specified views.

    • verticallyBetween(:UIView, and: UIView)
      Position the view between the two specified views vertically. The method layout the view's top and bottom edges. The order of the reference views is irrelevant. Note that the layout will be applied only if there is vertical space between the specified views.

    • verticallyBetween(:UIView, and: UIView, aligned: HorizontalAlign)
      Position the view between the two specified views vertically and aligned it using the specified HorizontalAlign. The view will be aligned related to the first specified reference view. Note that the layout will be applied only if there is vertical space between the specified views.

    Example:
       view.pin.verticallyBetween(viewA, and: viewB, aligned: .center).marginVertical(10)
    

    See Readme for more information

    Source code(tar.gz)
    Source code(zip)
  • 1.8.2(Aug 25, 2018)

    Add properties:

    • pin.readableMargins: UIEdgeInset:
      PinLayout's UIView.pin.readableMargins property expose UIKit UIView.readableContentGuide as an UIEdgeInsets. This is really useful since UIKit only expose the readableContent area to Auto Layout using UILayoutGuide.

    • pin.layoutmargins: UIEdgeInset
      PinLayout's UIView.pin.layoutMargins property expose directly the value of UIKit UIView.layoutMargins. The property exists only to be consistent with the other areas: pin.safeArea, pin.readableMargins and pin.layoutmargins. So its usage is not necessary.

    Add examples using these properties: pinlayout_example_layout_margins_all

    pinlayout_example_tableview_readable_content_all

    Source code(tar.gz)
    Source code(zip)
  • 1.8.1(Aug 23, 2018)

  • 1.8.0(Aug 21, 2018)

    BREAKING CHANGE: fitSize() has been removed after being deprecated for 10 months. sizeToFit(:FitType) should now be used instead. See Adjusting size.

    Plus:

    • Refactor relative positioning methods source code (above(...), after(...), ...) using a default parameter value for the alignment parameter.

    • Fix unit test screen density.

    • Update few examples source code.

    • Added by Luc Dion in Pull Request #167

    Source code(tar.gz)
    Source code(zip)
  • 1.7.12(Aug 16, 2018)

    • Add documentation that explains how PinLayout can handle view's animations.

      • Show few strategies that can be used to animate views.
    • Add animation examples using PinLayout

    • Convert fileprivate to private declarations

    • Add an new "Examples" markdown page showing all PinLayout's examples.

    • Added by Luc Dion in Pull Request #165

    Source code(tar.gz)
    Source code(zip)
  • 1.7.11(Aug 6, 2018)

    Method that position multiple edges now accept an offset parameter that specifies the distance from their superview's corresponding edges in pixels.

    New methods:

    • topLeft(_ offset: CGFloat)

    • topCenter(_ topOffset: CGFloat)

    • topRight(_ offset: CGFloat)

    • centerLeft(_ leftOffset: CGFloat)

    • center(_ offset: CGFloat)

    • centerRight(_ rightOffset offset: CGFloat)

    • bottomLeft(_ offset: CGFloat)

    • bottomCenter(_ bottomOffset: CGFloat)

    • bottomRight(_ offset: CGFloat)

    For example, to position a view at the top left corner with a top and left margin of 10 pixels:

       view.pin.topLeft(10)
    

    Other change

    Cleanup the interface by using default value parameters.

    Source code(tar.gz)
    Source code(zip)
  • 1.7.10(Jul 17, 2018)

    Add sizeToFit() method

    The method adjust the view's size based on the result of the method UIView.sizeToFit(). Particularly useful for controls/views that have an intrinsic size (label, button, ...).

    Source code(tar.gz)
    Source code(zip)
  • 1.7.9(Jun 28, 2018)

  • 1.7.8(Jun 26, 2018)

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

    • sizeToFit(:FitType) is not supported. Support for sizeToFit(:FitType) can be added to your custom CALayer subclasses, just make those layers conform to the SizeCalculable protocol and implement the two required functions.
    • CALayer.pin.safeArea property is not available.
    • aspectRatio() with no parameters

    See CALayer Support documentation for more information

    Source code(tar.gz)
    Source code(zip)
  • 1.7.7(Jun 20, 2018)

  • 1.7.6(Jun 15, 2018)

    PinLayout has moved to the layoutBox organization

    PinLayout is now part of the same organization as other open source projects related to layout using Swift.

    Refactor source code that handle size adjustment.

    Add an example using wrapContent() methods

    Refactor views frame/bounds access

    Source code(tar.gz)
    Source code(zip)
  • 1.7.5(Jun 6, 2018)

    Add wrapContent() methods that adjust view's width & height to wrap all its subviews.

    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:

    • wrapContent()
      wrapContent(padding: CGFloat)
      wrapContent(padding: UIEdgeInsets)
      Adjust the view's width and height to wrap all its subviews. The method also adjusts subviews position to create a tight wrap. It is also possible to specify an optional padding around all subviews.
    • wrapContent(:WrapType)
      wrapContent(:WrapType, padding: CGFloat)
      wrapContent(:WrapType, padding: UIEdgeInsets)
      Adjust the view's width AND/OR height to wrap all its subviews. WrapType values are .horizontally/.vertically/.all It is also possible to specify an optional padding around all subviews.

    See documentation for more information

    Added by Luc Dion in Pull Request #141

    Source code(tar.gz)
    Source code(zip)
  • 1.7.4(May 26, 2018)

Owner
layoutBox
Set of Swift layout related projects
layoutBox
Written in pure Swift, QuickLayout offers a simple and easy way to manage Auto Layout in code.

QuickLayout QuickLayout offers an additional way, to easily manage the Auto Layout using only code. You can harness the power of QuickLayout to align

Daniel Huri 243 Oct 28, 2022
Unxip - A blazing fast Xcode unarchiver

unxip unxip is a command line-tool designed for rapidly unarchiving Xcode XIP fi

Saagar Jha 598 Jan 7, 2023
A compact but full-featured Auto Layout DSL for Swift

Mortar allows you to create Auto Layout constraints using concise, simple code statements. Use this: view1.m_right |=| view2.m_left - 12.0 Instead of:

Jason Fieldman 83 Jan 29, 2022
Swift-picker-views - inline single and multi picker views for UIKit. Without tableview! Easy and simple

swift-picker-views Inline single and multiple picker views for UIKit. No tablevi

IBRAHIM YILMAZ 2 Jan 31, 2022
An Impressive Auto Layout DSL for iOS, tvOS & OSX. & It is written in pure swift.

KVConstraintKit KVConstraintKit is a DSL to make easy & impressive Auto Layout constraints on iOS, tvOS & OSX with Swift Installation Using CocoaPods

Keshav Vishwkarma 90 Sep 1, 2022
Auto Layout (and manual layout) in one line.

Auto Layout (and manual layout) in one line. Quick Look view.bb.centerX().below(view2).size(100) It’s equivalent to iOS 9 API: view.centerXAnchor.cons

Javier Zhang 74 Oct 19, 2022
Auto Layout made easy with the Custom Layout.

Auto Layout made easy with the Custom Layout. Getting started CocoaPods CocoaPods is a dependency manager for Cocoa projects. You can install it with

Malith Nadeeshan 1 Jan 16, 2022
The fast path to autolayout views in code

NorthLayout The fast path to autolayout views in code Talks https://speakerdeck.com/banjun/auto-layout-with-an-extended-visual-format-language at AltC

banjun 36 Jul 15, 2022
Harness the power of AutoLayout NSLayoutConstraints with a simplified, chainable and expressive syntax. Supports iOS and OSX Auto Layout

Masonry Masonry is still actively maintained, we are committed to fixing bugs and merging good quality PRs from the wider community. However if you're

null 18k Jan 5, 2023
The ultimate API for iOS & OS X Auto Layout — impressively simple, immensely powerful. Objective-C and Swift compatible.

The ultimate API for iOS & OS X Auto Layout — impressively simple, immensely powerful. PureLayout extends UIView/NSView, NSArray, and NSLayoutConstrai

PureLayout 7.6k Jan 6, 2023
Declarative Auto Layout in Swift, clean and simple

Tails Tails is a take on declarative Auto Layout. If you don't like typing (like me), it might be your kind of thing! Tails is written in Swift and cu

Nick Tymchenko 17 Jan 31, 2019
Intuitive and powerful Auto Layout library

Align introduces a better alternative to Auto Layout anchors. Semantic. Align APIs focus on your goals, not the math behind Auto Layout constraints. P

Alexander Grebenyuk 338 Oct 18, 2022
Very simple swipe-to-dismiss, supporting Auto Layout and dynamic heights

PanelPresenter Add swipe-dismiss logic to your view controller, supporting Auto Layout and dynamic heights. Installation Add this package to your proj

Pim 3 Aug 23, 2022
A declarative Auto Layout DSL for Swift :iphone::triangular_ruler:

Cartography ?? ?? Using Cartography, you can set up your Auto Layout constraints in declarative code and without any stringly typing! In short, it all

Robb Böhnke 7.3k Jan 4, 2023
Lightweight Swift framework for Apple's Auto-Layout

I am glad to share with you a lightweight Swift framework for Apple's Auto-Layout. It helps you write readable and compact UI code using simple API. A

null 349 Dec 20, 2022
Auto Layout In Swift Made Easy

Swiftstraints Swiftstraints can turn verbose auto-layout code: let constraint = NSLayoutConstraint(item: blueView, attr

null 119 Jan 29, 2022
Yet Another Swift Auto Layout DSL

FormationLayout Documentation FormationLayout is the top level layout class for one root view. FormationLayout takes a UIView as its rootView. transla

Evan Liu 53 Mar 31, 2022
Lightweight declarative auto-layout framework for Swift

SwiftyLayout SwiftyLayout is a framework that allows to describe layout constraints (ie NSLayoutConstraint) as a simple mathematical formula in a Swif

Hisakuni Fujimoto 15 Nov 7, 2017
Minimal Auto Layout in Swift

Restraint Restraint is a very very small library to help make your use of NSLayoutConstraint in Swift more legible & declarative. Like programmatic vi

The Puffin Supply Project 80 Aug 23, 2022