(Swift) Layout Constraints
A whiles back ago I was trying to learn how constraints
work. Today, I still have no idea what the heck is going on.
While learning, I can across a bunch of tutorials that helped. However, I don’t remember what blog I particularly liked.
This post contains a couple of classes that I made to help me add constraints to UIViews
.
Tools:
- Swift 3.0
- Xcode Version 8.0
Layouting
Below is a class named Layout
with a static function setupConstraint
that accepts the following parameters that lets me layer UIViews
that have either fixed or flexible widths and heights
- forView: UIView
// What UIView do you want to add constraints to?
- top: Int? = nil
// Top padding
- bottom: Int? = nil
// Bottom padding; Negative integer
- left: Int? = nil
// Left padding
- right: Int? = nil
// Right padding; Negative integer
- height: Int? = nil
Specified height
- fixedHeight: Bool = false
// Boolean defaulted to false; If you want a fixed UIView, give it a height (above)
- width: Int? = nil
// Specified width
- fixedWidth: Bool = false
// Boolean defaulted to false; If you want a fixed UIView, give it a width (above)
- selfView: UIViewController
// What is the UIViewController's view?
import UIKit
class Layout {
static func setupConstraint (forView: UIView, top: Int?=nil, bottom: Int?=nil, left: Int?=nil, right: Int?=nil, height: Int?=nil, fixedHeight: Bool = false, width: Int?=nil, fixedWidth: Bool = false, selfView: UIViewController) {
var parentViewController: UIView? {
return selfView.view
}
forView.translatesAutoresizingMaskIntoConstraints = false //USING AUTOLAYOUT NOT FRAMES
var l = NSLayoutConstraint() // LEFT
var r = NSLayoutConstraint() // RIGHT
var t = NSLayoutConstraint() // TOP
var b = NSLayoutConstraint() // BOTTOM
if (left != nil) {
l = forView.leadingAnchor.constraint(equalTo: (parentViewController?.leadingAnchor)!, constant: CGFloat(left!)) // LEFT
}
if (right != nil) {
r = forView.trailingAnchor.constraint(equalTo: (parentViewController?.trailingAnchor)!, constant: CGFloat(right!)) // RIGHT
}
if (top != nil) {
t = forView.topAnchor.constraint(equalTo: (parentViewController?.topAnchor)!, constant: CGFloat(top!)) // TOP
}
if (bottom != nil) {
b = forView.bottomAnchor.constraint(equalTo: (parentViewController?.bottomAnchor)!, constant: CGFloat(bottom!)) // BOTTOM
}
if (fixedHeight) {
// FIXED HEIGHT
let h = forView.heightAnchor.constraint(equalToConstant: CGFloat(height!))
parentViewController?.addConstraints([t, b, l, r, h])
} else if (fixedWidth) {
// FIXED WIDTH
let w = forView.widthAnchor.constraint(equalToConstant: CGFloat(width!))
parentViewController?.addConstraints([t, b, l, r, w])
} else if (fixedHeight == false && fixedWidth == false) {
// VARIABLE HEIGHT & WIDTH
parentViewController?.addConstraints([t, b, l, r])
}
}
}
Flexible Height and Width With Constraints on Top, Right, Bottom, and Left
This is an example of a UIView
with constraints on the top
, right
, bottom
, and left
side. When you rotate your simulator, the shape of the UIView
is flexible and the constraints, or fake margin, remain constant.
let myCyanUIView = UIView()
myCyanUIView.backgroundColor = UIColor.cyan
self.view.addSubview(myCyanUIView)
// Add layout constraint after adding my subview
Layout.setupConstraint(forView: myCyanUIView,
top: 50,
bottom: -50,
left: 50,
right: -50,
//height: 0, // I don't want a fixed height, so I can give this an arbitrary number or remove this paramater
fixedHeight: false, // <===
//width: 0, // I don't want a fixed width, so I can give this an arbitrary number or remove this paramater
fixedWidth: false, // <===
selfView: self)
Fixed Width With Constraints on Top, Bottom, and Left
This example is of a UIView
“pegged” to the left side of the viewport. The height will vary upon the orientation of the device.
let myRedUIView = UIView()
myRedUIView.backgroundColor = UIColor.red
self.view.addSubview(myRedUIView)
// Add layout constraint after adding my subview
Layout.setupConstraint(forView: myRedUIView,
top: 75,
bottom: -75,
left: 75,
right: nil, // <=== I want my UIView to be fixed on the left side
//height: 0,
fixedHeight: false, // <=== With no fixed height
width: 100, // <=== With a fixed width
fixedWidth: true, // <=== So, I set this to true
selfView: self)
Fixed Height With Constraints on Right, Bottom, and Left
This example is of a UIView
“pegged” to the bottom side of the viewport. The height is fixed and the width will vary upon the orientation of the device.
let myGreenUIView = UIView()
myGreenUIView.backgroundColor = UIColor.green
self.view.addSubview(myGreenUIView)
// Add layout constraint after adding my subview
Layout.setupConstraint(forView: myGreenUIView,
top: nil, // <===
bottom: -100,
left: 100,
right: -100,
height: 100, // <===
fixedHeight: true,
//width: 100, // <=== I want flexible width
fixedWidth: false, // <=== I want flexible width
selfView: self)
Stacking
This class, Stack
, lets me stack UIViews
. The private function accepts the following parameters:
- views:
[UIView]
// Array ofUIViews
- axis:
UILayoutConstraintAxis
- distribution:
UIStackViewDistribution
- alignment:
UIStackViewAlignment
- spacing:
Int? = 0
, - selfView:
UIViewController
, - parentContainer:
UIView? = nil
I can use this in conjunction with the Layout
class above.
import UIKit
class Stack {
static func setupConstraints (views: [UIView], axis: UILayoutConstraintAxis, distribution: UIStackViewDistribution, alignment: UIStackViewAlignment, spacing: Int?=0, selfView: UIViewController, parentContainer: UIView?=nil) {
var parentViewController: UIView! {
return selfView.view
}
var pContainer = UIView()
if (parentContainer != nil) {
pContainer = parentContainer!
} else {
pContainer = parentViewController
}
let stackView = UIStackView()
stackView.axis = axis
stackView.distribution = distribution
stackView.alignment = alignment
stackView.spacing = CGFloat(spacing!)
for view in views {
var height = Int()
var width = Int()
if (view.frame.size.height != 0.0 && view.frame.size.width != 0.0) {
height = Int(view.frame.size.height)
width = Int(view.frame.size.width)
} else {
height = 100
width = 100
}
stackView.addArrangedSubview(view)
view.heightAnchor.constraint(equalToConstant: CGFloat(height)).isActive = true
view.widthAnchor.constraint(equalToConstant: CGFloat(width)).isActive = true
}
stackView.translatesAutoresizingMaskIntoConstraints = false;
parentViewController.addSubview(stackView)
// Constraints FOR NESTING LOCATION
let l = stackView.leadingAnchor.constraint(equalTo: pContainer.leadingAnchor)
let t = stackView.topAnchor.constraint(equalTo: pContainer.topAnchor)
parentViewController.addConstraints([l, t])
}
}
Once you add this stacking
class to your project, you can consume it like so:
let myYellowUIView = UIView()
myYellowUIView.backgroundColor = UIColor.yellow
let myOrangeUIView = UIView()
myOrangeUIView.backgroundColor = UIColor.orange
let myBlueUIView = UIView()
myBlueUIView.backgroundColor = UIColor.blue
Stack.setupConstraints(views: [myYellowUIView, myOrangeUIView, myBlueUIView],
axis: .horizontal,
distribution: .fill,
alignment: .leading,
spacing: 20,
selfView: self,
parentContainer: myYellowUIView)
Layout.setupConstraint(forView: myYellowUIView,
top: 50,
//bottom: nil,
left: 20,
//right: nil,
height: 100,
fixedHeight: true,
width: 100,
fixedWidth: true,
selfView: self)
Unfortunately, I’m personally not very excited about the stacking because I didn’t flesh it out yet.
All it appears to be doing is what you can do when setting the frame's
CGRect
.
You can get the storyboard project is in this repo.