A Different Way To Layout Views Programmatically Using Swift

When starting to learn Swift I was confused by the auto-layout system. Wether using storyboards or programmatic constraints, It simply didn’t mesh well with me. Some constraints interfered with others at runtime, and it became frustrating to sort through all the different conflicting constraints. I admit that using auto-layout in Xcode is quite powerful, but I believe the additional complexity in addition to the increased computational costs of auto-layout calls for an alternate solution. Notice in my title I did not say this is a “better” way to layout views, just a different approach that could breed further experimentation

I felt there was a more concise, less computationally expensive way to lay out views, so I began working on a little side project I call View Layout. It’s simply an extension of UIView, where you’re instantiating a struct (ViewLayout) that calculates a view’s position for you. All that math you may do laying out views with CGRects and frames? It’s all encapsulated in the View Layout struct. It uses the ViewPosition enum to determine the position of the view in relation to a “guide view”. Instead of trying to describe it, i’ll just show the code. Here is the init for ViewLayout, with the steps broken down and explained.

// The different choices of position for the view you're laying out 
enum ViewPosition {
case bottomCenter
case bottomLeft
case bottomRight
case topCenter
case topLeft
case topRight
case center
case left
case right
}
// Usually you want to use the withFrame init unless laying out view with the root view as the guide
init(withFrame ofView: UIView, position: ViewPosition, size:CGSize, padding: (CGFloat,CGFloat)) {
self.guide = ofView.frame //1 
self.position = position //2
self.size = size //3
self.padding = padding //4
}

1: I set the frame that the ViewLayout object uses when calculating it’s origin

2: I set the position of the frame in comparison to the guide

3: I set the size of the frame I’m creating in ViewLayout

4: I set the horizontal and vertical padding of the view, using a tuple. The first item (padding.0) controls the vertical padding, with a positive number increasing the Y coordinate of the origin and a negative decreasing the Y. The second number does the same thing with the horizontal padding, positive numbers increasing the X coordinate and negative numbers decreasing it.

Now after calling this initializer, you have a ViewLayout object! You can’t use it yet, though. You’re just a function call away from getting a automatically configured frame to use with a view! All you have to do is call one of these functions:

// Don't forget to call this at the end of instantiation!
func makeInnerLayout() -> CGRect {
return CGRect(origin: origin, size: size)
}
func makeOuterLayout() -> CGRect {
return CGRect(origin: outerOrigin, size: size)
}

So what’s happening here? Well we’re using the outer or inner origin and the size passed into the initializer to return a CGRect to be used as a frame for a view. The origins are calculated properties in the ViewLayout object that return the origin of the view by using the padding, position, and guide view from the initializer. The difference between the two functions is that the makeOuterLayout() function simply calculates the origin to be outside of the guide view, while the makeInnerLayout() function calculates the origin to be inside of the view. If you wanted to layout two views next to each other with 8pts of padding, you would simply do it like this:

override func viewDidLoad() {
super.viewDidLoad()
let viewWidth = self.view.bounds.width / 2 - 12
//lays out first view inside the root view
let view1Layout = UIView.ViewLayout(withBounds: self.view, position: .topLeft, size: CGSize(width:viewWidth,height:200), padding: (50,0)).makeInnerLayout()
view1 = UIView(frame: view1Layout)
view1.backgroundColor = .black
self.view.addSubview(view1)
//uses view1 as a guide, is 8 points to the left
let view2Layout = UIView.ViewLayout(withFrame: view1, position: .right, size: CGSize(width:viewWidth,height:200), padding: (0,8)).makeOuterLayout()
view2 = UIView(frame: view2Layout)
view2.backgroundColor = .red
self.view.addSubview(view2)
}

This code produces a layout that looks like so:

Try to figure out how the code matches with the positions of these views- it will give you insight into the workings of ViewLayout

The big drawback to this approach and the reason many prefer autoLayout to CGRect math is you have to set the frames of the Views you’re laying out twice in your code, at least if you want to animate things. You set it up in ViewDidLoad, and in didRotate(or a similar function). I used to layout the views once in didLayoutSubviews(). But I found out later that this gets called during animation which usually ruins the animation if there is layout code pertaining to the view being animated in didLayoutSubviews().

I’m still trying to figure out how to handle animation and laying out in more difficult environments like split-screen and multitask using this method. If there are any comments or ways you think this could be accomplished, feel free to fork or send a pull request to the main project at https://github.com/afrench53198/ViewLayout.

This is my first post on medium writing about development and actual code, so let me know how I did! If anything was unclear or could be explained better just leave a comment. Thanks for reading, and I hope introducing the ViewLayout struct inspires you to create something better and more useful than AutoLayout programmatic constraints or Visual Formatting Language.