Dynamic Constraint Calculator for iOS

In today’s world of iOS development, graphical interface editors, especially Storyboards, gain more and more popularity. It’s because they continously improve, are easy to grasp, and provide a lot of features to make our lives easier. Not to mention, they save us from a ton of coding.

One of these key features is Auto Layout, the constraint-based layout system that was introduced years ago (I don’t think this is surprising or new to anyone). While it’s true that constraints can be created and managed programmatically, let’s face it: It’s a huge pain in the rear. The solution sounds easy, let’s use the graphical layout editor! However, when the layout needs changing, there are a few shortcomings in terms of flexibility .

There is a multitude of ways to keep our interfaces manageable. In this article, we’re going to take a look at a new approach to the flexibility of constraints. So whether you are a complete beginner at Auto Layout or not, read on!

dynamic constraint calculator for ios

Image: SlideShare

Problems ahead

Let’s say we need to create an app with multiple screens, all using Auto Layout. We add the UI elements and set up a dozen spacing and size constraints in all of the ViewControllers.

Now imagine that we (or heavens forbid, the client) change our minds and want all of the similar properties (like vertical spacing) changed. Now we have to readjust all of our carefully laid out constraints.

 

The easiest solutions

The “tunnel vision”

This is what you shouldn’t do (although we’ve all done it at one point of our lives): Greedily going through your ViewControllers, selecting all the constraints in question, and assigning the new values to the constants. Congratulations, you just spent about 10 minutes with something that you might have to repeat several times by the time your app looks satisfactory. This process is time consuming, prone to a lot of human errors (selecting the wrong constraint, accidentally changing something that we didn’t want to, formatting our entire hard drive, you name it).

The “outlet hell”

A lot smarter approach is to connect your constraints to their respective ViewControllers via outlets, and assign their values at runtime. This is easiest done with the Assistant Editor of XCode, drag-n-dropping your UI elements into your code, until you end up with something like this (note the name of the paragraph):

Making changes in our layout is a lot easier now in terms of time, but that’s not quite what we like to see in our classes, is it? Seemingly redundant assignments, “naked” integers, unnecessary lines of code.

To be fair, there are times when this solution isn’t that horrible, e.g. in case you have to apply some mathematical logic to them, but still, there is a lot of room for improvement.

 

A small step for mankind

The first improvement is not really black magic. Creating classes that handle constant values is generally considered a good practice, and the first step should be definitely this one. Let’s make a class called Constants, and set up a few values:

Now that we have these values stored independently from the view hierarchy, we can make some smarter changes to our ViewController(s):

Of course, this is only situationally correct, as our constraints don’t necessarily correlate with each other like this, but even if we have to modify multiple ViewControllers, we can use this class to our advantage (You get the idea). It’s a really powerful tool when our whole app has defining styles that are uniform everywhere. The same goes for colors, fonts, sizes and the list goes on, but we’re sticking to layout constraints for now.

Although constant-classes are handy when handling layout constraints, there a few drawbacks:

  1. The results can only be seen at runtime, which means that you need to rebuild the app, run it, then navigate to the screen (which can be pretty annoying in some situations). Yes, I know that there is such a thing as “preview” in the Assistant Editor, but it still won’t adapt to runtime values.
  2. Code. Code everywhere. And this is the main reason I’m writing this article, to reduce the lines of code that clutter our classes and make ViewControllers monstrous.

Basically, we’re left with these options: We either set up outlets for our constraints and manage them from code, while not even seeing the immediate results, or suffer through the manual adjustments.

Well, there’s another option: Programmatically changing the Storyboard.

 

Journey into the center of the Storyboard

So, what makes a UI descriptor file tick? Right clicking one and selecting “Open As -> Source Code” reveals its inner workings (You can switch back to the original view if you select “Interface Builder – Storyboard” here). Some are plain and simple, some are still readable while significantly larger, and some are downright disgusting (Picture over 10-20 relatively complex ViewControllers in the same file. It’s not pretty). The one thing they all have in common is that they are all XML files with the same structure. And when there’s structure, there’s also opportunity.

In this example, we have 8 UILabels, all centered horizontally, with various vertical spacings between them. This is how it looks inside the XML:

As you can see, apart from all the gibberish, inside the ViewController’s view property, all our labels are inside the <subviews> tag, and our constraints lurk below in silence. For the layout to be valid, I also added horizontal constraints, don’t mind the ones with “centerX” in them. At this moment, there’s not much we can do with them; the ordering is messed up and there are no accessible properties in the tags that we could use. If we change the constants’ value here, the interface builder will also update the constraints’ values, but that’s about it.

There are quite a few ways to exactly identify a constraint inside this XML. For starters, it has an ID that can be explicitly set in the constraint’s Attributes Inspector. Other ways are to set its tag in Attributes Inspector, and its user label in Identity Inspector. After looking through the editor, I’ve found that these properties are the best suited for identification purposes, mainly the Label, because it allows the setting of strings, not only integers (ID has to be unique, therefore it’s not adequate for “bulk” operations). Let’s change the first constraint’s Label property to “spacingType1” in the Identity Inspector, then check inside the XML to see the changes:

You probably noticed that the label we chose is identical to the static value inside our Constants class. This is where we step through the looking glass.

 

Shell Script Land

For the brave and/or bold, who are still with me after that terrifying chapter title, I have some good news: I’ve already written a script that updates constraint values from a Constants file, you’ll find the link at the end.

Delete your outlet connections from the ViewController, we no longer need them (don’t forget to remove the connections in the Interface Builder as well). Since there are no more constraint outlets, remove the constant assignments too, also in the ViewController.

Now I’d like to guide you through the details on how to achieve our goals. Here’s the basic gist:

We don’t want to write constant assignments in code, so we include a script which does exactly that for us. This script is executed before compilation, so the interface of the application will be built with the values we provided.

First, add a Run Script phase to your project, you’ll find it under “Build Phases” in the project’s settings, and make sure your script phase uses the standard /bin/sh shell. After adding the phase, drag-n-drop it just above the “Compile Sources” phase by holding on to the “Run Script” title. This ensures that the constraints get updated before compilation, so every change you made will be properly reflected on your next project run. Be careful, though! Mixing up the other phases’ ordering can produce some rather unpleasant results. Here’s what your ordering should look like:

dynamic constraint calculator for ios (1)

Whenever you build your app, this script will be executed, unless you specify otherwise. What we’re going to do is iterate through all our constant values in Constants.swift, find all the constraints in the storyboard whose userLabel property matches the variable name, then set the “constant” attribute of the XML element. Quick note: I’m not the biggest shell scripting pro, and I’m aware that some of the operations I used could be substituted or improved. Let’s start with reading the variables in Constants:

We read the file line by line, filter each line with grep, then remove all unnecessary characters, and assign the results into variables. We’ll use these variables to navigate the storyboard.

The “if” statements check whether the variables actually exist. If they are all correct, we find all the constraints that match their userLabels to the variable name. The first sed [more on sed – the editor] command removes the ‘constant=”x.y”‘ part from the XML element if it exists, and the second one “puts it back”. This is needed because if you set a constraint’s constant to zero, the “constant” attribute will be removed, and this way we ensure that it’s only included once.

The final script:

dynamic constraint calculator for ios (2)

Now build your app! You’ll instantly see that the constraints have been updated to reflect the values in your Constants file. True, it won’t automatically readjust the frames in Interface Builder, but a script that can do that would be only a few pages longer.

Note: If you wish to “regain control” of a constraint, make sure to remove the UserLabel property, otherwise its value will always be overwritten by the script.

 

Endless possibilities

Well, not quite, but there are other things that can just as easily be manipulated. Fonts, colors, etc. The point of this article was to give you some new ideas on how to save your ViewControllers from unnecessary code, making them cleaner.

The first script improvement would be to specify a list of Storyboards and Constants files, I’ll get around to it soon!

There is only so much we can (or should) directly access from a Storyboard, but it’s all well. Scripting is not meant to take over the place of constraint manipulation in native code, nor is it adequate to calculate complicated formulas, as it would be a bit too complex, but feel free to experiment and contribute!

Anyway, I hope you found this article useful! Finally, here’s the link to the GitHub gist: Click Me

 

Tamás Keller

Tamás Keller

I like my coffee how I like my code. Without bugs in it.