Amongst the many exciting things here at WWDC (Metal for OS X, UI Debugging, Open Source Swift...) is the introduction of a new layout class for Swift, UIStackView. UIStackView is a great addition to UIKit which offers a really simple way to leverage Auto Layout by arranging its subviews in rows or columns.
It's a bittersweet addition for me, since my very own Shinpuru Layout components did pretty much the same. That said, UIStackView contains a lot more functionality and is undoubtedly more performant than my code.
I've created a little Swift 2.0 application that demonstrates a few features of UIStackView: it dynamically sizes a handful of coloured boxes, changes layout directions based on orientation and allows the user to add or remove those boxes with a nice animation. To run this code, you'll need Xcode 7.
The application contains three UIStackView instances: mainStackView is always laid out vertically and contains stackView and a UISegmentedControl. stackView contains purple, red and blue boxes and subStackView which, in turn, contains grey, yellow and green boxes.
When the view is in landscape format (as above), stackView is laid out horizontally and subStackView is laid out vertically and when the view is rotated to portrait the opposite is true: stackView is laid out vertically and subStackView is laid out horizontally:
Toggling the items in the UISegmentedControls hides and shows the appropriate box.
Let's look at how I've implemented this.
First off, my three stack viewsare instantiated as UIStackViews:
let mainStackView = UIStackView()
let stackView = UIStackView()
let subStackView = UIStackView()
...and, inside viewDidLoad() of my view controller, added to the view and each other. When adding a subview to be managed by a stack view, the method is addArrangedSubview():
view.addSubview(mainStackView)
mainStackView.addArrangedSubview(stackView)
stackView.addArrangedSubview(subStackView)
Here, I also add the coloured boxes and segmented control to the relevant stack views. For example:
stackView.addArrangedSubview(blueBox)
subStackView.addArrangedSubview(grayBox)
mainStackView.addArrangedSubview(segmentedControl)
I want to fill out the available space in the main stack view, but keep the intrinsic size of the UISegmentedControl, while with the other two stack views, I want to resize all their sub views so that all the available space is used. To do that, the distribution policy I use for mainStackView is Fill, but the the other two, it's FillEqually:
mainStackView.distribution = UIStackViewDistribution.Fill
stackView.distribution = UIStackViewDistribution.FillEqually
subStackView.distribution = UIStackViewDistribution.FillEqually
The main stack view is always laid out vertically, so I set its axis in viewDidLoad() too:
mainStackView.axis = UILayoutConstraintAxis.Vertical
To "pin" my main stack view to the available size of the application, I override viewDidLayoutSubviews() and set its frame:
let top = topLayoutGuide.length
let bottom = bottomLayoutGuide.length
mainStackView.frame = CGRect(x: 0, y: top, width: view.frame.width, height: view.frame.height - top - bottom).rectByInsetting(dx: 10, dy: 10)
...and it's also here that I examine the dimensions of the view.frame and, set the axis properties of the two other stack views depending on whether the view is portrait or landscape. This seems to happen in a background thread, so I've wrapped that code in a dispatch_async() on the main queue:
dispatch_async(dispatch_get_main_queue())
{
ifself.view.frame.height> self.view.frame.width
{
self.stackView.axis = UILayoutConstraintAxis.Vertical
self.subStackView.axis = UILayoutConstraintAxis.Horizontal
}
else
{
self.stackView.axis = UILayoutConstraintAxis.Horizontal
self.subStackView.axis = UILayoutConstraintAxis.Vertical
}
}
func segmentedControlChangeHandler()
{
let index = segmentedControl.selectedSegmentIndex
let togglingView = index <= 3 ? stackView.arrangedSubviews[index] : subStackView.arrangedSubviews[index - 3]
UIView.animateWithDuration(0.25){ togglingView.hidden = !togglingView.hidden }
}
I did notice some issues when running this in debug build configuration, so be sure to use release.
The code for this experiment lives in my GitHub repository here. Enjoy!
Of course, the big question is: does this code work OK with the new multitasking support for iPad Air 2? Yessiree bob cat tail! Check this out...