Skip to content

Instantly share code, notes, and snippets.

@fabiogiolito
Created November 28, 2018 15:06
Show Gist options
  • Save fabiogiolito/478b10b0d31df62259aab72956db4ec5 to your computer and use it in GitHub Desktop.
Save fabiogiolito/478b10b0d31df62259aab72956db4ec5 to your computer and use it in GitHub Desktop.
Swift Layout Extension
import UIKit
extension UIView {
// ----------------------------------------------------------------------
// Make it cleaner to add all your elements as subviews in a single line.
// Example:
// view.addSubviews([coverImage, titleLabel, followButton, …])
func addSubviews(_ views: [UIView]) {
for view in views {
self.addSubview(view)
}
}
// ====================================
// MARK:- AUTOLAYOUT
// ----------------------------------------------------------------------
// Main anchor helper. Anchors edges, set width/height.
// All params optional if you don't need to anchor a specific side.
// Example:
// 1. Anchor a button to the top left edge of the view.
// button.anchor(
// top: view.safeAreaLayoutGuide.topAnchor,
// left: view.layoutMarginsGuide.leftAnchor,
// paddingTop: 16
// )
// 2. Anchor element's width and height.
// avatar.anchor(width: 100, height: 100)
func anchor(top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, paddingTop: CGFloat = 0, paddingLeft: CGFloat = 0, paddingBottom: CGFloat = 0, paddingRight: CGFloat = 0, width: CGFloat = 0, height: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true
}
if let left = left {
leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true
}
if let right = right {
rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true
}
if width != 0 {
widthAnchor.constraint(equalToConstant: width).isActive = true
}
if height != 0 {
heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
// ----------------------------------------------------------------------
// Anchor something over another view.
// All params optional if you don't need to anchor a specific side.
// Example:
// 1. Anchor a badge image to the bottom right of the avatar image.
// badge.anchorOver(
// avatar,
// bottom: -8,
// right: -8
// )
func anchorOver(_ view: UIView, top: CGFloat? = nil, left: CGFloat? = nil, bottom: CGFloat? = nil, right: CGFloat? = nil) {
if let top = top {
self.anchor(top: view.safeAreaLayoutGuide.topAnchor, paddingTop: top)
}
if let left = left {
self.anchor(left: view.safeAreaLayoutGuide.leftAnchor, paddingLeft: left)
}
if let bottom = bottom {
self.anchor(bottom: view.safeAreaLayoutGuide.bottomAnchor, paddingBottom: bottom)
}
if let right = right {
self.anchor(right: view.safeAreaLayoutGuide.rightAnchor, paddingRight: right)
}
}
// ----------------------------------------------------------------------
// Fill the superview
// The .fillSuperview() is a shorthand for .anchorOver() matching all edges to the superview.
// You can specify a vertical or horizontal padding.
// Example:
// textViewBlock.addSubview(textView)
// textView.fillSuperview(paddingVertical: 8, paddingHorizontal: 8)
func fillSuperview(paddingVertical: CGFloat = 0, paddingHorizontal: CGFloat = 0) {
guard let superview = self.superview else { return }
self.anchorOver(superview, top: paddingVertical, left: paddingHorizontal, bottom: paddingVertical, right: paddingHorizontal)
}
// ----------------------------------------------------------------------
// Fill the superview respecting side margins.
// If the superview covers the full width and you're setting horizontal padding,
// you might want to just pin your view to the side margins instead of specifying a static number for padding.
// Example:
// userBar.addSubview(avatarAndUsernameStackView)
// avatarAndUsernameStackView.fillSuperviewToSideMargins()
func fillSuperviewToSideMargins(paddingVertical: CGFloat = 0) {
self.anchor(
top: superview?.safeAreaLayoutGuide.topAnchor,
left: superview?.layoutMarginsGuide.leftAnchor,
bottom: superview?.safeAreaLayoutGuide.bottomAnchor,
right: superview?.layoutMarginsGuide.rightAnchor,
paddingTop: paddingVertical,
paddingBottom: paddingVertical
)
}
// ----------------------------------------------------------------------
// Define Direction and Alignment options
enum Direction { case below, right, left, above }
enum Alignment { case top, bottom, left, right, center }
// ----------------------------------------------------------------------
// Place an element above, below, to the right or left of another element already anchored.
// Example:
// 1. The username goes centered 8pts below the user avatar.
// username.anchorPlaced(.below, view: avatar, distance: 8)
//
// 2. Two images side by side, aligned at the top.
// image2.anchorPlaced(.right, view: image1, distance: 8, align: .top)
func anchorPlaced(_ direction: Direction, view: UIView, distance: CGFloat = 0, align: Alignment = .center) {
switch direction {
case .below:
self.anchor(top: view.safeAreaLayoutGuide.bottomAnchor, paddingTop: distance)
case .right:
self.anchor(left: view.safeAreaLayoutGuide.rightAnchor, paddingLeft: distance)
case .left:
self.anchor(right: view.safeAreaLayoutGuide.leftAnchor, paddingRight: distance)
case .above:
self.anchor(bottom: view.safeAreaLayoutGuide.topAnchor, paddingBottom: distance)
}
switch align {
case .top:
self.anchor(top: view.safeAreaLayoutGuide.topAnchor)
case .bottom:
self.anchor(bottom: view.safeAreaLayoutGuide.bottomAnchor)
case .left:
self.anchor(left: view.safeAreaLayoutGuide.leftAnchor)
case .right:
self.anchor(right: view.safeAreaLayoutGuide.rightAnchor)
case .center:
if direction == .above || direction == .below {
self.anchorCenterX(view)
} else {
self.anchorCenterY(view)
}
}
}
// ----------------------------------------------------------------------
// Anchor elements with full width below each other.
// Most components end up being blocks that go from edge to edge.
// On instagram for example you have the userBar, image, actionsBar, commentsBlock…
// You can just anchor one element below the other.
// Note that this takes an anchor as as reference, not a view. So you can anchor the first element to the view's topAnchor, while everything eles will be anchored to the bottomAnchor of the previous element.
// Example:
// UserBar.anchorBlockBelow(view.safeAreaLayoutGuide.topAnchor)
// image.anchorBlockBelow(userBar.bottomAnchor)
// actionsBar.anchorBlockBelow(image.bottomAnchor)
// commentsBlock.anchorBlockBelow(actionsBar.bottomAnchor)
func anchorBlockBelow(_ referenceAnchor: NSLayoutYAxisAnchor, distance: CGFloat = 0, height: CGFloat = 0) {
self.anchor(
top: referenceAnchor,
left: superview?.safeAreaLayoutGuide.leftAnchor,
right: superview?.safeAreaLayoutGuide.rightAnchor,
paddingTop: distance,
height: height
)
}
// ----------------------------------------------------------------------
// Centering anchors.
// You can use these centering helpers to align your view to the center of another view, either vertical, horizontal or both.
// Example:
// avatar.anchorCenterX(coverImage)
// You can specify a positive or negative distance to the center to offset it, but that's rarely necessary.
func anchorCenterX(_ view: UIView, distance: CGFloat = 0) {
self.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: distance).isActive = true
}
func anchorCenterY(_ view: UIView, distance: CGFloat = 0) {
self.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: distance).isActive = true
}
func anchorCenterIn(_ view: UIView, distanceX: CGFloat = 0, distanceY: CGFloat = 0) {
self.anchorCenterX(view, distance: distanceX)
self.anchorCenterY(view, distance: distanceY)
}
// ----------------------------------------------------------------------
// Ratio anchor
// It's called anchorSquare because most common use is to make things squared.
// But you can set any ratio and it will constraint the height to the width * ratio.
// Example:
// avatar.anchorSquare()
// video.anchorSquare(ratio: 0.5625) // 16:9 ratio
func anchorSquare(ratio: CGFloat = 1) {
self.heightAnchor.constraint(equalTo: self.widthAnchor, multiplier: ratio).isActive = true
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment