EasyAnchor
- Push Hero - pure Swift native macOS application to test push notifications
- PastePal - Pasteboard, note and shortcut manager
- Quick Check - smart todo manager
- Alias - App and file shortcut manager
- My other apps
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.
Read more How to make Auto Layout more convenient in iOS
Auto Layout APIs history
How new APIs were introduced over the years, so you know to set your deployment target
NSLayoutConstraint
since iOS 6, macOS 10.7isActive
since iOS 8, macOS 10.10NSLayoutAnchor
,UI|NSLayoutGuide
since 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
addSubview
so that view is in hierarchy - Set
translatesAutoresizingMaskIntoConstraints = false
- Set
isActive = true
to enable constraints
Most of the time, NSLayoutAnchor
is what you need. But if you need more, EasyAnchor 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.top
Activate 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.height
Relation
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, left
Convenient 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-5
Padding
a.anchor.paddingHorizontally(20) // leading+20, trailing-20
b.anchor.paddingVertically(20) // top+20, bottom-20
Size
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==20
You can't just use constant
because EasyAnchor will infer to the superview
c.anchor.width.constant(20) // width==superview.width+20
Ratio
a.anchor.height.equal.to(a.anchor.width) // height==width
Alternatively, you can just use ratio
a.anchor.width.constant(10)
a.anchor.height.ratio(2) // height==width*2
a.anchor.height.constant(10)
a.anchor.width.ratio(2) // width==height*2
Inference
You know what you mostly want to do. So does EasyAnchor
superview
Most of the time, you want to constraint to the a.anchor.top.left // a.top == a.superview.top, a.left == a.superview.left
Most 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.width
Find 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 = 100
Animation
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 EasyAnchor 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
EasyAnchor 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
Group
to declare many sets of constraints for different screen sizes / size classes - Use ratio, read Auto Layout with different screen sizes
Installation
EasyAnchor is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod 'EasyAnchor'
EasyAnchor is also available through Carthage. To install just write into your Cartfile:
github "onmyway133/EasyAnchor"
EasyAnchor 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 EasyAnchor, check the CONTRIBUTING file for more info.
License
EasyAnchor is available under the MIT license. See the LICENSE file for more info.