Last active
May 6, 2016 08:45
-
-
Save duemunk/e0005bc865fbcba594e6 to your computer and use it in GitHub Desktop.
Auto Layout in Swift
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
// | |
// AutoLayout.swift | |
// Tobias Due Munk | |
// | |
// Created by Tobias Due Munk on 26/08/14. | |
// Copyright (c) 2014 Tobias Due Munk. All rights reserved. | |
// | |
// Example usage: | |
// | |
// view1.topInset(20, relation: .Equal, priority: 200) | |
// view1.topInset(20, relation: .GreaterThanOrEqual) | |
// view1.horizontalInset(0) | |
// view2.horizontalInset(0) | |
// view2.topOffset(view1, value: 0) | |
// view2.heightEqual(view1) | |
// | |
// or | |
// | |
// view.inset(top: 0, left: 10) | |
// view.width(100, .GreaterThanOrEqual) | |
import UIKit | |
extension UIView { | |
/* Inset */ | |
func inset(insetValue: Float, priority: UILayoutPriority = 1000) { | |
inset(top: insetValue, left: insetValue, bottom: insetValue, right: insetValue, priority: priority) | |
} | |
func inset(top: Float? = nil, left: Float? = nil, bottom: Float? = nil, right: Float? = nil, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
if let left = left { | |
leftInset(left, margin: margin, relation: relation, priority: priority) | |
} | |
if let top = top { | |
topInset(top, margin: margin, relation: relation, priority: priority) | |
} | |
if let right = right { | |
rightInset(right, margin: margin, relation: relation, priority: priority) | |
} | |
if let bottom = bottom { | |
bottomInset(bottom, margin: margin, relation: relation, priority: priority) | |
} | |
} | |
func horizontalInset(_ insetValue: Float = 0, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
inset(left: 0, right: 0, margin: margin, relation: relation, priority: priority) | |
} | |
func verticalInset(_ insetValue: Float = 0, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
inset(top: 0, bottom: 0, margin: margin, relation: relation, priority: priority) | |
} | |
func leftInset(_ value: Float = 0, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
inset(margin ? .LeftMargin : .Left, value: value, relation: relation, priority: priority) | |
} | |
func topInset(_ value: Float = 0, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
inset(margin ? .TopMargin : .Top, value: value, relation: relation, priority: priority) | |
} | |
func rightInset(_ value: Float = 0, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
let (flippedValue, flippedRelation) = flip(value: value, relation: relation) | |
inset(margin ? .RightMargin : .Right, value: flippedValue, relation: flippedRelation, priority: priority) | |
} | |
func bottomInset(_ value: Float = 0, margin: Bool = false, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) -> NSLayoutConstraint { | |
let (flippedValue, flippedRelation) = flip(value: value, relation: relation) | |
return inset(margin ? .BottomMargin : .Bottom, value: flippedValue, relation: flippedRelation, priority: priority) | |
} | |
func bottomBaselineInset(_ value: Float = 0, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
let (flippedValue, flippedRelation) = flip(value: value, relation: relation) | |
inset(.Baseline, value: flippedValue, relation: flippedRelation, secondAttribute: .Bottom, priority: priority) | |
} | |
private func inset(attribute: NSLayoutAttribute, value: Float = 0, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) -> NSLayoutConstraint { | |
let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: self.superview, attribute: attribute, multiplier: 1, constant: CGFloat(value)) | |
constraint.priority = priority | |
return installConstraint(constraint) | |
} | |
private func inset(attribute: NSLayoutAttribute, value: Float = 0, relation: NSLayoutRelation = .Equal, secondAttribute: NSLayoutAttribute, priority: UILayoutPriority = 1000) -> NSLayoutConstraint { | |
let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: self.superview, attribute: secondAttribute, multiplier: 1, constant: CGFloat(value)) | |
constraint.priority = priority | |
return installConstraint(constraint) | |
} | |
/* Height and width */ | |
func width(width: Float, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
dimension(.Width, value: width, relation: relation, priority: priority) | |
} | |
func height(height: Float, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
dimension(.Height, value: height, relation: relation, priority: priority) | |
} | |
private func dimension(attribute: NSLayoutAttribute, value: Float, relation: NSLayoutRelation, priority: UILayoutPriority = 1000) -> NSLayoutConstraint { | |
let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: CGFloat(value)) | |
constraint.priority = priority | |
return installConstraint(constraint) | |
} | |
/* Center */ | |
func center(relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
centerX(priority: priority) | |
centerY(priority: priority) | |
} | |
func centerX(relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
center(.CenterX, relation: relation, priority: priority) | |
} | |
func centerY(relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
center(.CenterY, relation: relation) | |
} | |
private func center(attribute: NSLayoutAttribute, value: Float = 0, relation: NSLayoutRelation, priority: UILayoutPriority = 1000) -> NSLayoutConstraint { | |
let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: self.superview, attribute: attribute, multiplier: 1, constant: CGFloat(value)) | |
constraint.priority = priority | |
return installConstraint(constraint) | |
} | |
/* Relation */ | |
func topOffset(toView: UIView, value: Float = 0, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
relate(.Top, toView: toView, toAttribute: .Bottom, value: value, relation: relation, priority: priority) | |
} | |
func rightOffset(toView: UIView, value: Float = 0, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
let (flippedValue, flippedRelation) = flip(value: value, relation: relation) | |
relate(.Right, toView: toView, toAttribute: .Left, value: flippedValue, relation: flippedRelation, priority: priority) | |
} | |
func heightEqual(toView: UIView, relation: NSLayoutRelation = .Equal, priority: UILayoutPriority = 1000) { | |
relate(.Height, toView: toView, toAttribute: .Height, relation: relation, priority: priority) | |
} | |
private func relate(attribute: NSLayoutAttribute, toView: UIView, toAttribute: NSLayoutAttribute, value: Float = 1, relation: NSLayoutRelation, priority: UILayoutPriority = 1000) { | |
let constraint = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: toView, attribute: toAttribute, multiplier: 1, constant: CGFloat(value)) | |
constraint.priority = priority | |
let installedConstraint = installConstraint(constraint) | |
} | |
/* Clean up */ | |
func removeAllConstraints() { | |
for installedConstraint in relatedConstraints() { | |
installedConstraint.active = false | |
} | |
} | |
/* Private helpers */ | |
private func relatedConstraints() -> [NSLayoutConstraint] { | |
var directConstraints = self.constraints() as [NSLayoutConstraint] | |
if let indirectConstraints = relatedConstraintsInSuperViews(self) { | |
directConstraints += indirectConstraints | |
} | |
return directConstraints | |
} | |
private func relatedConstraintsInSuperViews(view: UIView) -> [NSLayoutConstraint]? { | |
return relatedConstraintsInSuperViews(view, relatedToView: view) | |
} | |
private func relatedConstraintsInSuperViews(view: UIView, relatedToView: UIView) -> [NSLayoutConstraint]? { | |
if let superview = view.superview { | |
var relatedConstraints = [NSLayoutConstraint]() | |
for constraint in superview.relatedConstraints() { | |
if let firstItem = constraint.firstItem as? UIView { | |
if firstItem == relatedToView { | |
relatedConstraints.append(constraint) | |
} | |
} else { | |
if let secondItem = constraint.secondItem as? UIView { | |
if secondItem == relatedToView { | |
relatedConstraints.append(constraint) | |
} | |
} | |
} | |
} | |
// Recursively add constraint in super view | |
if let superRelatedConstraints = relatedConstraintsInSuperViews(superview, relatedToView: relatedToView) { | |
for constraint in superRelatedConstraints { | |
relatedConstraints.append(constraint) | |
} | |
} | |
if relatedConstraints.count > 0 { | |
return relatedConstraints | |
} | |
return nil | |
} | |
return nil | |
} | |
private func installConstraint(constraint: NSLayoutConstraint) -> NSLayoutConstraint { | |
if let installedConstraint = constraintSimilarToConstraint(constraint) { | |
installedConstraint.constant = constraint.constant | |
return installedConstraint | |
} else { | |
setTranslatesAutoresizingMaskIntoConstraints(false) | |
constraint.active = true | |
return constraint | |
} | |
} | |
private func constraintSimilarToConstraint(constraint: NSLayoutConstraint) -> NSLayoutConstraint? { | |
for installedConstraint in relatedConstraints() { | |
let installedSecondItem = installedConstraint.secondItem as? UIView | |
let secondItem = constraint.secondItem as? UIView | |
if installedConstraint.firstAttribute == constraint.firstAttribute && | |
installedConstraint.relation == constraint.relation && | |
installedSecondItem == secondItem && | |
installedConstraint.secondAttribute == constraint.secondAttribute { | |
return installedConstraint | |
} | |
} | |
return nil | |
} | |
private func flip(#value: Float, relation: NSLayoutRelation) -> (flippedValue: Float, flippedRelation: NSLayoutRelation) { | |
let flippedValue = -value | |
let flippedRelation: NSLayoutRelation = { | |
switch relation { | |
case .GreaterThanOrEqual: return .LessThanOrEqual | |
case .LessThanOrEqual: return .GreaterThanOrEqual | |
default: return relation | |
} | |
}() | |
return (flippedValue, flippedRelation) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment