Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reworked layout system #1185

Merged
merged 11 commits into from
Nov 1, 2018
Merged

Reworked layout system #1185

merged 11 commits into from
Nov 1, 2018

Conversation

OrkhanAlikhanov
Copy link
Contributor

@OrkhanAlikhanov OrkhanAlikhanov commented Oct 26, 2018

Layout

Working directly with NSLayoutConstraint or NSLayoutAnchor is pretty tedious. Material gives a simple API to create and update constraints easily.

layoutcode will add childView to parentView and return instance of Layout:

parentView.layout(childView)

Layout has completely chainable API to set constraints fluently. For example, to make a childView have the constant height of 32, half of the width of its parent plus 20 point, 10 points below anotherView's bottom and in the horizontal center of its parent you just do:

parentView.layout(childView)
  .height(32) // constant height of 32
  .width(offset: 20).multiply(0.5) // parentWidth * 0.5 + 20
  .centerX() // center in parent in X coordinate
  .top(anotherView.anchor.bottom, 10) // anotherView's bottom + 10

List of available methods

Layout has following methods to set constraints in relation with parent and all takes optional offset argument:

top(_ offset:) // Constraints top of the view to its parent's. 
left(_ offset:) // Constraints left of the view to its parent's. 
right(_ offset:) // Constraints right of the view to its parent. 
leading(_ offset:) // Constraints leading of the view to its parent's. 
trailing(_ offset:) // Constraints trailing of the view to its parent. 
bottom(_ offset:) // Constraints bottom of the view to its parent's. 
topLeft(top:, left:) // Constraints top-left of the view to its parent's. 
topRight(top:, right:) // Constraints top-right of the view to its parent's. 
bottomLeft(bottom:, left:) // Constraints bottom-left of the view to its parent's. 
bottomRight(bottom:, right:) // Constraints bottom-right of the view to its parent's. 
leftRight(left:, right:) // Constraints left and right of the view to its parent's. 
topLeading(top:, leading:) // Constraints top-leading of the view to its parent's. 
topTrailing(top:, trailing:) // Constraints top-trailing of the view to its parent's. 
bottomLeading(bottom:, leading:) // Constraints bottom-leading of the view to its parent's. 
bottomTrailing(bottom:, trailing:) // Constraints bottom-trailing of the view to its parent's. 
leadingTrailing(leading:, trailing:) // Constraints leading and trailing of the view to its parent's. 
topBottom(top:, bottom:) // Constraints top and bottom of the view to its parent's. 
center(offsetX:, offsetY:) // Constraints center of the view to its parent's. 
centerX(_ offset:) // Constraints horizontal center of the view to its parent's. 
centerY(_ offset:) // Constraints vertical center of the view to its parent's. 
width(offset:) // Constraints width of the view to its parent's. 
height(offset:) // Constraints height of the view to its parent's. 
edges(top:, left:, bottom:, right:) // Constraints edges of the view to its parent's. 

Methods for width and height

There are 2 additional methods for setting constant height and width:

width(_ width:) // Constraints width of the view to a constant value. 
height(_ height:) // Constraints height of the view to a constant value. 

If you noticed there are 2 methods for width and height:

width(offset:) // Constraints width of the view to its parent's. 
height(offset:) // Constraints height of the view to its parent's.

width(_ width:) // Constraints width of the view to a constant value. 
height(_ height:) // Constraints height of the view to a constant value. 

The ones with offset are for constraining to parent and others to a constant. Following code would help to better distinguish them:

view.layout(button)
  .width(offset: 0) /// constraint to parent with offset 0.
  .width() /// same as above.
  .width(10) /// constraint to a constant value 10.

Mehtods other than width and height does not take offset: as named parameter take value, rather they are marked with _ just like constant width/height:

  .top(0) /// constraint to parent with offset 0. No `offset:`
  .top() /// same as above.

Anchors

To create constraints in relation to other view, LayoutAnchor can be used. All methods available for set constraints between child and parent are also available for anchoring on Layout:

top(_ anchor:, _ offset:) // Constraints top of the view to the given anchor. 
left(_ anchor:, _ offset:) // Constraints left of the view to the given anchor. 
right(_ anchor:, _ offset:) // Constraints right of the view to the given anchor. 
leading(_ anchor:, _ offset:) // Constraints leading of the view to the given anchor. 
trailing(_ anchor:, _ offset:) // Constraints trailing of the view to the given anchor. 
bottom(_ anchor:, _ offset:) // Constraints bottom of the view to the given anchor. 
topLeading(_ anchor:, top:, leading:) // Constraints top-leading of the view to the given anchor. 
topTrailing(_ anchor:, top:, trailing:) // Constraints top-trailing of the view to the given anchor. 
bottomLeading(_ anchor:, bottom:, leading:) // Constraints bottom-leading of the view to the given anchor. 
bottomTrailing(_ anchor:, bottom:, trailing:) // Constraints bottom-trailing of the view to the given anchor. 
leadingTrailing(_ anchor:, leading:, trailing:) // Constraints leading and trailing of the view to the given anchor. 
topLeft(_ anchor:, top:, left:) // Constraints top-left of the view to the given anchor. 
topRight(_ anchor:, top:, right:) // Constraints top-right of the view to the given anchor. 
bottomLeft(_ anchor:, bottom:, left:) // Constraints bottom-left of the view to the given anchor. 
bottomRight(_ anchor:, bottom:, right:) // Constraints bottom-right of the view to the given anchor. 
leftRight(_ anchor:, left:, right:) // Constraints left and right of the view to the given anchor. 
topBottom(_ anchor:, top:, bottom:) // Constraints top and bottom of the view to the given anchor. 
center(_ anchor:, offsetX:, offsetY:) // Constraints center of the view to the given anchor. 
centerX(_ anchor:, _ offset:) // Constraints horizontal center of the view to the given anchor. 
centerY(_ anchor:, _ offset:) // Constraints vertical center of the view to the given anchor. 
width(_ anchor:, _ offset:) // Constraints height of the view to the given anchor. 
height(_ anchor:, _ offset:) // Constraints height of the view to the given anchor. 
edges(_ anchor:, top:, left:, bottom:, right:) // Constraints edges of the view to the given anchor. 

Anchor of a view can be accessed via view.anchor:

view.layout(button)
  .top(anotherView.anchor.bottom, 10) // anotherView's bottom + 10

List of available anchors:

.top // A layout anchor representing top of the view.
.bottom // A layout anchor representing bottom of the view.
.left // A layout anchor representing left of the view.
.right // A layout anchor representing right of the view.
.leading // A layout anchor representing leading of the view.
.trailing // A layout anchor representing trailing of the view.
.topLeft // A layout anchor representing top-left of the view.
.topRight // A layout anchor representing top-right of the view.
.bottomLeft // A layout anchor representing bottom-left of the view.
.bottomRight // A layout anchor representing bottom-right of the view.
.topLeading // A layout anchor representing top-leading of the view.
.topTrailing // A layout anchor representing top-trailing of the view.
.bottomLeading // A layout anchor representing bottom-leading of the view.
.bottomTrailing // A layout anchor representing bottom-trailing of the view.
.topBottom // A layout anchor representing top and bottom of the view.
.leftRight // A layout anchor representing left and right of the view.
.leadingTrailing // A layout anchor representing leading and trailing of the view.
.center // A layout anchor representing center of the view.
.centerX // A layout anchor representing horizontal center of the view.
.centerY // A layout anchor representing vertical center of the view.
.edges // A layout anchor representing top, left, bottom and right of the view.
.width // A layout anchor representing width of the view.
.height // A layout anchor representing height of the view.

Safe Anchor

For pinning to the safeAreaLayoutGuide of a view, instead of view.anchor, view.safeAnchor can be used. For below iOS 11, view.safeAnchor will fall back to use view.anchor.

view.layout(button)
  .leading(view.safeAnchor.leading)

Omitting anchors

Anchors can be omitted if they are same as the anchor that's being pinned:

view.layout(button1)
  .width(button2.anchor.width)
  .width(button2) // same as above

Same applies to safeAnchor as well:

view.layout(button1)
	.width(view.safeAnchor.width)
  .width(view.safeAnchor) // same as above

Compound constraints

Some constraints such as edges and center are composed of multiple constraints. For example edges creates 4 constraints. Those constrains can be used together with anchors:

view.layout(button)
  .center() // pin to the center of parent same as centerX().centerY()
  .topLeft(button2.anchor.bottomRight) // button.top = button2.bottom and bottom.left = bottom.right.
  .edges(view2) /// equal to view2's top left bottom right.

Priority and multiplier

Priority and multiplier of be set using priority() and multiply() methods respectively:

view.layout(button)
  .width().multiply(0.5) /// CGFloat multiplier
          .priority(100) /// Float
  .height(32).priority(.defaultLow) /// UILayoutPriority

Limitations

priority() and multiply() methods can only be used to set last created constraint. For example, edges() creates 4 constraints and the last one is right constraint, so priority() and multiply() will be effective on only it. You have to explicitly delare

view.layout(button).edges()
  .multiply(0.5) // only right constraint gets multiplier 0.5

Updating constraints

Layout will update the constant of the constraint if it exists:

view.layout(button)
.top()

DispatchQueue.main.asyncAfter(deadline: .now() + 1) { // after 1 second.
  self.view.layout(button)
    .top(20) /// Update the constant of top constraint instead of creating new one.

  UIView.animate(withDuration: 0.25, animations: self.view.layoutSubviews) // animate change
}

Relation

Only NSLayoutConstraint.Relation.equal is supported right now, lessThanOrEqual and greaterThanOrEqual is not supported yet.

Some highlights of changes

  • Renaming:

    • centerHorizontally(offset:) -> centerX(offset:)
    • centerVertically(offset:) -> centerY(offset:)
    • horizontally(left:, right:) -> leftRight(left:, right:)
    • vertically(top:, bottom:) -> topBottom(top:, bottom:)
  • Removed:

    • size(_ size:)
    • horizontally(_ children:, left:, right:, interimSpace:)
    • vertically(_ children:, top:, bottom:, interimSpace:,

Sample app

Button.zip
See ButtonViewController.swift line 36

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

2 participants