Created
November 28, 2018 15:06
-
-
Save fabiogiolito/478b10b0d31df62259aab72956db4ec5 to your computer and use it in GitHub Desktop.
Swift Layout Extension
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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