Auto Layout: quick prototyping and shared UI components with Visual Format Language and JSONLayout

Maxim Volgin
3 min readFeb 14, 2019

--

Auto Layout is extremely powerful. It is also extremely verbose, when using it from code. Using it from the Xcode Interface Builder provides more visual feedback but creating and verifying the constraints requires plenty of clicking around. And there is also Visual Format Language. It is has somewhat limited functionality, for instance it is not possible to specify ratio with it, and applying it in code is cumbersome beyond measure. So basically we have three standard options, all of them suboptimal in their own way.

Let’s say we do understand to some extent how Auto Layout works, and we want to type less, read less, click less, prototype UI quickly and be able to reuse created UI components easily. How can we achieve it? There are probably many answers to this question, but for now let’s settle on a decision to use some kind of declarative mark-up language to define the structure and style of the UI element. Wait, but we already have a declarative mark-up language for style, and that is Visual Format Language itself! The only thing missing is a mark-up language for structure. Considering the built-in support in Swift via Codable protocol, JSON seems to be the natural choice for this role.

Let’s have a look at JSONLayout framework that does just that, and try to build a calculator layout with it. We would probably like to do it in a separate view controller that we can later embed into a container view of another view controller. Let’s start with the layout document and call it calculator.json -

{  "views": {    "button7": { "type": "UIButton"},    "button8": { "type": "UIButton"},    "button9": { "type": "UIButton"},    "buttonA": { "type": "UIButton"},    "button4": { "type": "UIButton"},    "button5": { "type": "UIButton"},    "button6": { "type": "UIButton"},    "buttonM": { "type": "UIButton"},    "button1": { "type": "UIButton"},    "button2": { "type": "UIButton"},    "button3": { "type": "UIButton"},    "buttonP": { "type": "UIButton"},    "button0": { "type": "UIButton"},    "buttonC": { "type": "UIButton"},    "buttonE": { "type": "UIButton"},  },  "metrics": {    "gap": 8.0  },  "constraints": {    "row1": "H:|-gap-[button7(>=10)]-gap-[button8(==button7)]-gap-[button9(==button7)]-gap-[buttonA(==button7)]-gap-|",    "row2": "H:|-gap-[button4(==button7)]-gap-[button5(==button7)]-gap-[button6(==button7)]-gap-[buttonM(==button7)]-gap-|",    "row3": "H:|-gap-[button1(==button7)]-gap-[button2(==button7)]-gap-[button3(==button7)]-gap-[buttonP(==button7)]-gap-|",    "row4": "H:|-gap-[button0]-gap-[buttonC(==button7)]-gap-[buttonE(==button7)]-gap-|",    "col1": "V:|-gap-[button7(>=10)]-gap-[button4(==button7)]-gap-[button1(==button7)]-gap-[button0(==button7)]-gap-|",    "col2": "V:|-gap-[button8(==button7)]-gap-[button5(==button7)]-gap-[button2(==button7)]-gap-[button0(==button7)]-gap-|",    "col3": "V:|-gap-[button9(==button7)]-gap-[button6(==button7)]-gap-[button3(==button7)]-gap-[buttonC(==button7)]-gap-|",    "col4": "V:|-gap-[buttonA(==button7)]-gap-[buttonM(==button7)]-gap-[buttonP(==button7)]-gap-[buttonE(==button7)]-gap-|"  }}

It does look understandable from the first glance, does it not? Now let’s implement method viewDidLoad() of our embedded view controller.

let color: [String: UIColor] = [  "buttonA": UIColor.orange,  "buttonM": UIColor.orange,  "buttonP": UIColor.orange,  "buttonE": UIColor.orange]let title: [String: String] = [  "buttonA": "A",  "buttonM": "-",  "buttonP": "+",  "buttonE": "=",  "buttonC": ",",  "button9": "9",  "button8": "8",  "button7": "7",  "button6": "6",  "button5": "5",  "button4": "4",  "button3": "3",  "button2": "2",  "button1": "1",  "button0": "0"]try? Layout(name: "calculator")  .configure {    $0.didCreateView = { (view, id) in      switch view {      case let button as UIButton:        button.backgroundColor = color[id] ?? UIColor.darkGray        button.setTitle(title[id], for: .normal)      default:        break      }    }  }  .inflate(in: view)view.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1.0).isActive = trueview.backgroundColor = UIColor.lightGray

As we can see, using our layout is pretty straightforward. We need to specify 1:1 ratio in code though, as it is not supported by Visual Format Language. We also need to define titles in code, but was it not a calculator, we would be using localized strings anyway. Now it’s time to behold the result and contemplate how much time would it take to do the same thing in code, or in Interface Builder, and how readable and reusable would it be.

Our calculator in all its glory!

--

--