VL – a F# DSL for the Layout of IOS Controls

Starting with IOS 6 Apple added Auto Layout – a language for specifying constraints on the layout of IOS controls. With Auto Layout one can express the height, width, sequence, spacing, margins, anchoring / docking, etc. of controls, as constraints.

Auto Layout constraints can be expressed in an untyped way as strings or one can use the underlying API. While feasible for Objective-C code, these approaches become awkward for other languages such as F#.

The VL DSL is meant to easily create IOS layouts in code in a strongly typed manner. The DSL makes use of F# Algebraic Data Types (Discriminated Unions), pattern matching and operator overloading. (Note that at the time of writing this post Xamarin has announced Xamarin.Forms which may make VL redundant).

Examples:
Auto Layout :       H:| -10-[ctrl1]-[ctrl2]-10-|
VL                :       H [ !- 10. ; @! ctrl1 ; sp ; @! ctrl2;  !- 10. ]

The two constraints are equivalent; they layout two controls horizontally with 10 point margins at the left and right edges and a standard space in between.

VL is not as concise as Auto Layout string format but it has other advantages which makes the overall UI work easier in F#.

To start, consider the two screen shots below which are from the VL Github test project. Its the same screen laid out in portrait and landscape modes.

VLPortrait

 

VLLandscape

The screen has 4  buttons at the corners and a table in the middle. The buttons are anchored to the corners and the table is meant to stretch with the screen. The following constraints were used to express this layout:

//constraints

//anchor buttons horizontally
let cth = H [ !- 2. ; !@ bTopLeft   ; !->= 5.1; !@ bTopRight   ; !- 2.]
let cbh = H [ !- 2. ; !@ bBottomLeft; !->= 5.2; !@ bBottomRight; !- 2.]

//anchor buttons vertically
let clv = V [ !- 20.; !@ bTopLeft; !->= 5.3; !@ bBottomLeft; !- 2.]
let crv = V [ !- 20.; !@ bTopRight; !->= 5.4; !@ bBottomRight; !- 2.]

//constraints to stretch the centre table
let c5 = H [!- 40. ; !@ cntntTbl @@ [!!> 5.5]; !- 40.]
let c6 = V [!- 60. ; !@ cntntTbl @@ [!!> 5.6]; !- 40.]

Lets delve a little deeper:

Constraints “cth” and “cbh” are for horizontal spacing of the buttons and “clv” and “crv” for vertical. Constraints “c5” and “c6” are for the table. (The table contents, in the screenshots, show the Auto Layout translations of these constraints).

The “H […]” and “V […]” functions are for specifying horizontal and vertical layouts, respectively. If the first element of the parameter list is spacing (margin) then the view (which should be the next element) will be anchored to the left (for ‘H’) and the top (for ‘V’). If the first element is a view reference (see next paragraph) then the view is not anchored to the left or top. Similar logic is applicable to the last element.

The “!@” prefix operator is a way of referring to an IOS control (UIView) in a constraint statement.

Note that the button constraints make use of the  “!->” operator which is used express that spacing between two controls (or a control and an edge) has to be greater than some value. Putting this condition in the horizontal and vertical constraints keeps the constraints satisfiable. This is because the buttons are anchored to the edges and the spacing between them can vary based on the screen size or orientation. Fixing the spacing between the buttons to a constant will make the buttons stretch instead.

Similarly the constraints for the table have the “@@ [!!> 5.x]” pattern. This allows the height and width to be greater than 5.x and so the table can expand to fill the available space. The “@@” operator comes after a view reference and associates a list of conditions for the width or height of the control (depending on whether the ‘H’ or ‘V’ function, respectively, is used).

Note the constant values 5.1, 5.2, etc. are sequenced to make the constraints easily identifiable in the corresponding Auto Layout format. This was done only to make the sample more understandable. The VL processor code generates temporary names for controls for generating the Auto Layout strings before making the underlying API calls. These names are not needed after the API call is done. The Auto Layout versions show the temporary names which makes it hard to match the corresponding constraints between the two languages.

Once the base controls and constraints are defined, they can be ‘packaged’ together to define a UIView. For example, the following VL function call makes a new UIView control which has all of the controls referenced in the constraints as subviews (children) and all of the constraints applied to the created, container UIView:

let uiview = package [cth; cbh; clv; crv; c5; c6]

Because the constraints already reference the controls we can just use them for packaging.

The base controls can be packaged up into multiple containers and these containers can in turn be packaged into other containers to create the layout hierarchy.

A word of advice: make sure you have both vertical and horizontal constraints for all controls – even nested containers that were generated using the package function.

The VL language does not prevent all types of errors; some of the errors will surface at runtime. For example if you accidentally package the same basic control into two different containers.

Read the Auto Layout guide to get the concepts. Then peruse the VL code to understand how to express the constraints in VL. The core VL file is only 225 lines so should be fairly easy to comprehend.

1 thought on “VL – a F# DSL for the Layout of IOS Controls

  1. Pingback: F# Weekly #23, 2014 | Sergey Tihon's Blog

Leave a comment