Some people don’t like Interface Builder and like to code instead. Sometimes your layout is so dynamic you need to lay out in code. In either case, programmatic layout of views becomes the answer instead of auto layout. In Xcode 7 Apple introduced stack views. In an earlier post, I introduced stack views in Interface Builder. There are still some problems with stack views in Interface Builder, but those problems do not appear in code. In this lesson I’ll introduce how to use stack views in code.
The Theory
Stacks views are really simple: they are arrays of views with a few properties. You initialize the stackview with an array of views and set a few properties: axis
, distribution
, fill
and spacing
. At run time, the views create constraints the fulfill those requirements.
By doing this you can almost avoid auto layout with one exception: you need to use auto layout to layout the stack view on the superview. In our lesson, we’ll layout our stack view over the entire view and then have a view that works with lots of embedded views and stack views.
Set up the Project
Create a new Universal project in Swift called StackViewCodeDemo. Since Xcode no longer marks the launchscreen, add a label to the launchscreen.storyboard with the title StackViewCodeDemo. I usually center this by clicking the align menu button and then center horizontally and vertically in the container and updating the constraints like this:
Since the is programmatic, we need nothing on the storyboard.
The Button Generator
I’m going to use a series of buttons for my view objects. Go to the ViewController.swift code and add the following declarations to the class:
//MARK: Properties let colorDictionary = [ "Red":UIColor( red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0), "Green":UIColor( red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0), "Blue":UIColor( red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0), ]
This dictionary gives us a color name for a key and a UIColor
for a value. Now add the follow function to the class:
//MARK: Instance methods func colorButton( withColor color:UIColor, title:String) -> UIButton { let newButton = UIButton(type: .System) newButton.backgroundColor = color newButton.setTitle( title, forState: .Normal) newButton.setTitleColor( UIColor.whiteColor(), forState: .Normal) return newButton }
I’m keeping this simple. I built a method to make a button quickly with certain properties set. I left off the action, but you can certainly put it in here. I did this so I can iterate through my dictionary and make buttons. Make another method with this code:
func displayKeyboard(){ //generate an array of buttons var buttonArray = [UIButton]() for (myKey,myValue) in colorDictionary{ buttonArray += [colorButton( withColor: myValue, title: myKey) ] }
We have an array of buttons that take their color and title from the dictionary.
Making a Stack View
We are now ready to make a stack view and add it to the superview. I’ll make one that will have the three buttons next to each other. Under the displayKeyboard
we just wrote, add the following:
let stackView = UIStackView(arrangedSubviews: buttonArray) stackView.axis = .Horizontal stackView.distribution = .FillEqually stackView.alignment = .Fill stackView.spacing = 5 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView)
The arrangedSubviews
parameter of the UIStackView
initializer takes an array of UIView
objects and makes a stack view from it. We set the properties for the stack view, and shut off the autoresizing mask. I used a .Fill
distribution which will stretch the buttons vertically to take the entire view. The distribution
property makes equal width buttons. I set the buttons 5 points away from each other.
This stack view needs autolayout, which is why it was critical to shut off the autoresizing mask. We’ll lay out the stack view to be the view with a margin. I pinned the view to the left and right margins which are 20 points from the left and right sides, and 30 points above and below the top and bottom of the superview. Add this to your code:
//autolayout the stack view - pin 30 up 20 left 20 right 30 down let viewsDictionary = ["stackView":stackView] let stackView_H = NSLayoutConstraint.constraintsWithVisualFormat( "H:|-20-[stackView]-20-|", //horizontal constraint 20 points from left and right side options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary) let stackView_V = NSLayoutConstraint.constraintsWithVisualFormat( "V:|-30-[stackView]-30-|", //vertical constraint 30 points from top and bottom options: NSLayoutFormatOptions(rawValue:0), metrics: nil, views: viewsDictionary) view.addConstraints(stackView_H) view.addConstraints(stackView_V)
I’ll leave this code as-is. It works fine for a template to use the entire view for a stack View. If you want to know more about Auto layout in code, see my post on it.
Finally add the method to the viewDidLoad
override func viewDidLoad() { super.viewDidLoad() displayKeyboard() }
Build and run. You will have three buttons:
Rotate and the buttons automatically change size and shape.
Nesting Stack Views
Once we have one stack view, we can also nest stack views. comment out the following code:
/* let stackView = UIStackView(arrangedSubviews: buttonArray) stackView.axis = .Horizontal stackView.distribution = .FillEqually stackView.alignment = .Fill stackView.spacing = 5 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView) */
Add underneath it the following code:
let subStackView = UIStackView(arrangedSubviews: buttonArray) subStackView.axis = .Horizontal subStackView.distribution = .FillEqually subStackView.alignment = .Fill subStackView.spacing = 5 //set up a label let label = UILabel() label.text = "Color Chooser" label.textColor = UIColor.whiteColor() label.backgroundColor = UIColor.blackColor() label.textAlignment = .Center let blackButton = colorButton(withColor: UIColor.blackColor(), title: "Black")
I made a stack view like our last stack view, a label with white text on a black background, and a black button. Under that code add this:
let stackView = UIStackView(arrangedSubviews: [label,subStackView,blackButton]) stackView.axis = .Vertical stackView.distribution = .FillEqually stackView.alignment = .Fill stackView.spacing = 10 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView)
We make a vertical stack view with the three views we defined. The order of the array is left to right in horizontal and top to bottom in vertical. I kept the same distribution and alignment as our first example, but added 10 points between the vertical view. Since our auto layout constraints for stackView
are already set, we are ready to build and run. Check the view in both portrait and landscape.
Without a lot of coding for layout, stack views can make some quick adaptive layouts with a minimal amount of auto layout. Apple’s documentation encourages using stack views for the easiest and most adaptive layout, making coding for devices from a iPhone to an AppleTV easy. I kept this simple. In a real app, you probably want to add actions and have your views defined outside the displayKeyboard
method as properties. You might want to play around with that and with changing the properties of the stack view to see the variation you can get with a stack view.
The Whole Code
// // ViewController.swift // StackViewCodeDemo // // Created by Steven Lipton on 11/11/15. // Copyright © 2015 MakeAppPie.Com. All rights reserved. // import UIKit class ViewController: UIViewController { //MARK: Properties let colorDictionary = [ "Red":UIColor(red: 1.0, green: 0.0, blue: 0.0, alpha: 1.0), "Green":UIColor(red: 0.0, green: 1.0, blue: 0.0, alpha: 1.0), "Blue":UIColor(red: 0.0, green: 0.0, blue: 1.0, alpha: 1.0), ] //MARK: Instance methods func colorButton(withColor color:UIColor, title:String) -> UIButton{ let newButton = UIButton(type: .System) newButton.backgroundColor = color newButton.setTitle(title, forState: .Normal) newButton.setTitleColor(UIColor.whiteColor(), forState: .Normal) return newButton } func displayKeyboard(){ //generate an array var buttonArray = [UIButton]() for (myKey,myValue) in colorDictionary{ buttonArray += [colorButton(withColor: myValue, title: myKey)] } /* //Iteration one - singel stack view let stackView = UIStackView(arrangedSubviews: buttonArray) stackView.axis = .Horizontal stackView.distribution = .FillEqually stackView.alignment = .Fill stackView.spacing = 5 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView) */ //Iteration two - nested stack views //set up the stack view let subStackView = UIStackView(arrangedSubviews: buttonArray) subStackView.axis = .Horizontal subStackView.distribution = .FillEqually subStackView.alignment = .Fill subStackView.spacing = 5 //set up a label let label = UILabel() label.text = "Color Chooser" label.textColor = UIColor.whiteColor() label.backgroundColor = UIColor.blackColor() label.textAlignment = .Center let blackButton = colorButton(withColor: UIColor.blackColor(), title: "Black") let stackView = UIStackView(arrangedSubviews: [label,subStackView,blackButton]) stackView.axis = .Vertical stackView.distribution = .FillEqually stackView.alignment = .Fill stackView.spacing = 10 stackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(stackView) //autolayout the stack view - pin 30 up 20 left 20 right 30 down let viewsDictionary = ["stackView":stackView] let stackView_H = NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[stackView]-20-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary) let stackView_V = NSLayoutConstraint.constraintsWithVisualFormat("V:|-30-[stackView]-30-|", options: NSLayoutFormatOptions(rawValue:0), metrics: nil, views: viewsDictionary) view.addConstraints(stackView_H) view.addConstraints(stackView_V) } //MARK: Life Cycle override func viewDidLoad() { super.viewDidLoad() displayKeyboard() } }
Leave a Reply