Anchors
Table of contents
- Story
- Features
- Basic with Anchor
- Inference
- Find existing constraints
- Animation
- Update constraints with Group
- Extensible with ConstraintProducer
- Build quickly with Builder
- Debug Auto Layout
- Support multiple screen sizes
Story
I like to build view in code, so Auto Layout is my definite choice. The syntax has improved over the years, but I always want to do it with minimum effort. More repetitive code makes you tend to do copy paste and produce more bugs.
Auto Layout APIs history
How new APIs were introduced over the years, so you know to set your deployment target
NSLayoutConstraintsince iOS 6, macOS 10.7isActivesince iOS 8, macOS 10.10NSLayoutAnchor,UI|NSLayoutGuidesince iOS 9, macOS 10.11
Do you need another Auto Layout framework?
All the Auto Layout frameworks you see are just convenient ways to build NSLayoutConstraint, in fact these are what you normally need
- Call
addSubviewso that view is in hierarchy - Set
translatesAutoresizingMaskIntoConstraints = false - Set
isActive = trueto enable constraints
Most of the time, NSLayoutAnchor is what you need. But if you need more, Anchors can be your choice.
Examples
Tetris
Well, you can use Auto Layout to make Tetris. Auto Layout plays well with affine transform too. See code
activate(
lineBlock.anchor.left.bottom
)
// later
activate(
firstSquareBlock.anchor.left.equal.to(lineBlock.anchor.right),
firstSquareBlock.anchor.bottom
)
// later
activate(
secondSquareBlock.anchor.right.bottom
)Piano
This is how to make a piano using apply and fixed spacing. See code
activate(
c.anchor.left,
b.anchor.right,
c.anchor.top.bottom,
c.anchor.top.bottom.width.apply(to: [d, e, f, g, a, b]),
c.anchor.fixedSpacingHorizontally(togetherWith: [d, e, f, g, a, b], spacing: 0)
)
activate(
cd.anchor.top,
cd.anchor.size.equal.to(c.anchor.size).multiplier(2/3),
cd.anchor.top.size.apply(to: [de, fg, ga, ab]),
cd.anchor.centerX.equal.to(c.anchor.right),
de.anchor.centerX.equal.to(d.anchor.right),
fg.anchor.centerX.equal.to(f.anchor.right),
ga.anchor.centerX.equal.to(g.anchor.right),
ab.anchor.centerX.equal.to(a.anchor.right)
)More
More example can be found in Example
Features
- Fluent builder syntax
- Easy to customize with protocol based
- Support iOS, macOS
- Support
LayoutGuide - Update and reset constraints
- Find existing constraints
- Debug constraints
- Visualize constraints
Basic with Anchor
Access Anchor
Prefer composition over extension, this is how you access anchor. Support View, LayoutGuide, LayoutSupport
let view = UIView()
view.anchor.left.right
let guide = UILayoutGuide()
guide.anchor.width.height
topLayoutGuide.anchor.bottom
bottomLayoutGuide.anchor.topActivate constraints
Use activate which accepts variadic parameters. This is important, no matter how you create constraints, they don't have any effect until you activate it ❗❗❗❗❗
activate(
a.anchor.top.left,
b.anchor.top.right,
c.anchor.bottom.left,
d.anchor.bottom.right
)Attributes
Supports all attributes you can think of
anchor.top.left.bottom.right
.leading.trailing
.topMargin.bottomMargin.leftMargin.rightMargin
.centerX.centerY
.centerXWithinMargins.centerXWithinMargins
.lastBaseline.firstBaseline
.width.heightRelation
a.anchor.top.equal.to(b.bottom)
a.anchor.width.greaterThanOrEqual.to(b.anchor.height)
a.anchor.width.lessThanOrEqual.to(b.anchor)Configuration
This is how to apply constant, multiplier, priority, identifier
a.anchor.top.equal.to(b.anchor.bottom)
.constant(10).multiplier(1.5).priority(999)
.id("verticalSpacingBetweenA-B")Reference
Get references to constraints to modify it later on. In the ref closure, we get access to all the created constraints
var constraint: NSLayoutConstraint?
activate(
view.anchor.center.constant(10).ref({ constraint = $0.first })
)Convenient attributes
Use convenient attributes which combine multiple inner attributes
a.anchor.center // centerX, centerY
a.anchor.size // width, height
a.anchor.edges // top, right, bottom, leftConvenient methods
Insets
a.anchor.edges.insets(EdgeInsets(top: 1, left: 2, bottom: 3, right: 4)) // top+1, left+2, bottom+3, right+4
a.anchor.edges.insets(5) // top+5, left+5, bottom-5, right-5Padding
a.anchor.paddingHorizontally(20) // leading+20, trailing-20
b.anchor.paddingVertically(20) // top+20, bottom-20Size
Size to another anchor
a.anchor.height.equal.to(b.anchor.width)
c.anchor.size.equal.to(d.anchor)Size to a constant
a.anchor.height.equal.to(20) // height==20
b.anchor.size.equal.to(20) // width==20, height==20You can't just use constant because Anchors will infer to the superview
c.anchor.width.constant(20) // width==superview.width+20Ratio
a.anchor.height.equal.to(a.anchor.width) // height==widthAlternatively, you can just use ratio
a.anchor.width.constant(10)
a.anchor.height.ratio(2) // height==width*2a.anchor.height.constant(10)
a.anchor.width.ratio(2) // width==height*2Inference
You know what you mostly want to do. So does Anchors
Most of the time, you want to constraint to the superview
a.anchor.top.left // a.top == a.superview.top, a.left == a.superview.leftMost of the time, you want to constraint to the same attributes
a.anchor.top.bottom.width.equal.to(b.anchor) // a.top == b.top, a.bottom == b.bottom, a.width == b.widthFind existing constraints
You don't need to declare variables to store constraints, you can just retrieve them back
a.anchor.find(.height)?.constant = 100
// later
b.anchor.find(.height)?.constant = 100
// later
c.anchor.find(.height)?.constant = 100Animation
Animation is simple. You just change your constraint 's isActive or constant properties, then layoutIfNeeded in an animation block. You can use UIView.animate or UIViewPropertyAnimator
// Change constraint
a.anchor.find(.height)?.constant = 100
loginButtonHeightConstraint.isActive = false
let animator = UIViewPropertyAnimator(duration: 1, dampingRatio: 0.7)
animator.addAnimations { [weak self] in
self?.view.layoutIfNeeded()
}
animator.startAnimation(afterDelay: 1)Update constraints with Group
activate is just a convenient way to produce group, then set isActive on the Group. If you just want to group a set of constraints, then set isActive later on, use function group
In this example, we have 4 groups, then take turn to toggle which group gets activated
func toggle(_ group: Group) {
[g1, g2, g3, g4].forEach { g in
guard let g = g else {
return
}
g.isActive = (g == group)
}
}
g1 = group(a.anchor.top.left)
g2 = group(a.anchor.top.right)
g3 = group(a.anchor.bottom.right)
g4 = group(a.anchor.bottom.left)
g1.isActive = true
animator = Animator(view: self, animations: [
{
self.toggle(self.g2)
},
{
self.toggle(self.g3)
},
{
self.toggle(self.g4)
},
{
self.toggle(self.g1)
}
])
animator.start()Extensible with ConstraintProducer
Group is a set of NSLayoutConstraint, which are produced by ConstraintProducer
public protocol ConstraintProducer {
func constraints() -> [NSLayoutConstraint]
}For now, there is Anchor and Builder which conforms to ConstraintProducer, you can extend Anchors easily by conform to ConstraintProducer. For example
// This accepts a list of views, and build constraints
class MyAwesomeLayout: ConstraintProducer {
init(views: [UIView]) {
// Your code goes here
}
func constraints() -> [NSLayoutConstraint] {
// Your code goes here
return []
}
}
let awesomeLayout = MyAwesomeLayout(views: [view1, view2])
activate(awesomeLayout)Build quickly with Builder
Well, Anchor is for making constraints between 2 views. If you want to make constraints for multiple views at once, you can use multiple Anchor. There are some tasks that you do often, let Builder help you. These method below use Builder under the hood
Anchors has a set of builders to help you avoid repetitive tasks and build UIs quickly
Apply
Apply the same anchor to other views
a.anchor.left.height.apply(to: [b, c]),Paging
Build a paging scrollView horizontally
addSubview(scrollView)
[a, b, c, d].forEach {
scrollView.addSubview($0)
}
activate(
scrollView.anchor.edges.insets(8),
a.anchor.pagingHorizontally(togetherWith: [b, c, d], in: scrollView)
)Fixed spacing
Add fixed spacing. The views will resize
activate(
container.anchor.edges.insets(8),
a.anchor.left.top.bottom,
c.anchor.right,
a.anchor.top.bottom.width.apply(to: [b, c]),
a.anchor.fixedSpacingHorizontally(togetherWith: [b, c], spacing: 50)
)Dynamic spacing
Add dynamic spacing using LayoutGuide. The spacing will resize
activate(
container.anchor.edges.insets(8),
a.anchor.size.equal.to(30),
b.anchor.size.equal.to(30),
c.anchor.size.equal.to(30),
a.anchor.left.centerY,
a.anchor.centerY.apply(to: [b, c]),
c.anchor.right,
a.anchor.dynamicSpacingHorizontally(togetherWith: [b, c])
)Debug Auto Layout
Support multiple screen sizes
- Use
Groupto declare many sets of constraints for different screen sizes / size classes - Use ratio, read Auto Layout with different screen sizes
Installation
Anchors is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'Anchors'Anchors is also available through Carthage. To install just write into your Cartfile:
github "onmyway133/Anchors"Anchors can also be installed manually. Just download and drop Sources folders in your project.
Author
Khoa Pham, [email protected]
Contributing
We would love you to contribute to Anchors, check the CONTRIBUTING file for more info.
License
Anchors is available under the MIT license. See the LICENSE file for more info.









