Skip to content

Instantly share code, notes, and snippets.

@slodge
Created April 4, 2014 21:09
Show Gist options
  • Save slodge/9983213 to your computer and use it in GitHub Desktop.
Save slodge/9983213 to your computer and use it in GitHub Desktop.
If you've watched any of the N+1 series - http://mvvmcross.wordpress.com/ - then you'll no doubt have seen me writing a lot of repetitive, error-prone layout code like:
var textView = new UITextField(new RectangleF(10, 100, 300, 30));
Add(textView);
textView.InputView = picker;
var label = new UILabel(new RectangleF(10, 130, 300, 30));
Add(label);
All of this repetitive, error-prone layout code was... of course... unnecessary.  The problem was that I am a dinosaur and sometimes it takes me time to learn what I should be doing...
iOS6 is now almost a year old - and part of iOS6 was a new layout system called **constraints**. The basic idea behind these constraints is that it allows you to specify relationships between the layouts of UIView objects and their attribute values- so that you can, for example, ask one view to set its Top equal to the Bottom of another view. When you do this, then iOS/UIKit will then try to work out the layout for you at runtime.
I've been playing with these today and they are **fabulous** - especially when coupled with the power of C# - expect to see more of them in my demos soon!
One gist of code that really makes this lovely is Frank's Easy Layout DSL - see [http://praeclarum.org/post/45690317491/easy-layout-a-dsl-for-nslayoutconstraint](http://praeclarum.org/post/45690317491/easy-layout-a-dsl-for-nslayoutconstraint)
This expression based library let's you use simple C# statements to define your layout - it's best summarised by code - see his picture which shows how to layout a button and a text box:
[![](http://media.tumblr.com/f0aeb31cc97efeaa9c91d3d7c91c77bd/tumblr_inline_mjvfrxd0Ai1qz4rgp.png)](http://media.tumblr.com/f0aeb31cc97efeaa9c91d3d7c91c77bd/tumblr_inline_mjvfrxd0Ai1qz4rgp.png)
For my experiments I decided to see if I could create a Fluent-style API for the same type of effect. I've nothing against the 'Easy Layout DSL' - I just wanted to learn the constraints for myself, plus I wanted to see if using a Fluent approach gave me more composability and reusability.
What I wanted to do was to see if I could define Frank's 'text and button' layout using Fluent code like:
View.AddConstraints(
button.AtTopOf(View).Plus(vPadding),
button.AtRightOf(View).Minus(hPadding),
button.Width().EqualTo(ButtonWidth),
text.AtLeftOf(View, hPadding),
text.ToLeftOf(button, hPadding),
text.WithSameTop(button)
);
It turned out that it took a bit longer than I had hoped - there were a few gotchas along the way, mainly to do with "TranslateAutoresizingMaskIntoConstraints" - but within a couple of hours I had this working :)
And once I had that working, I then started to play....
**What would a form layout look like?**
[![](http://2.bp.blogspot.com/-wX4OGEiSGZw/Ue0qqXWYWxI/AAAAAAAAA_A/3uXN6JExEe0/s320/quicklayout.png)](http://2.bp.blogspot.com/-wX4OGEiSGZw/Ue0qqXWYWxI/AAAAAAAAA_A/3uXN6JExEe0/s1600/quicklayout.png)
```
View.AddConstraints(
fNameLabel.AtTopOf(View, vMargin),
fNameLabel.AtLeftOf(View, hMargin),
fNameLabel.ToLeftOf(sNameLabel, hMargin),
sNameLabel.WithSameTop(fNameLabel),
sNameLabel.AtRightOf(View, hMargin),
sNameLabel.WithSameWidth(fNameLabel),
fNameField.WithSameWidth(fNameLabel),
fNameField.WithSameLeft(fNameLabel),
fNameField.Below(fNameLabel, vMargin),
sNameField.WithSameLeft(sNameLabel),
sNameField.WithSameWidth(sNameLabel),
sNameField.WithSameTop(fNameField),
numberLabel.WithSameLeft(fNameLabel),
numberLabel.ToLeftOf(streetLabel, hMargin),
numberLabel.Below(fNameField, vMargin),
numberLabel.WithRelativeWidth(streetLabel, 0.3f),
streetLabel.WithSameTop(numberLabel),
streetLabel.AtRightOf(View, hMargin),
numberField.WithSameLeft(numberLabel),
numberField.WithSameWidth(numberLabel),
numberField.Below(numberLabel, vMargin),
streetField.WithSameLeft(streetLabel),
streetField.WithSameWidth(streetLabel),
streetField.WithSameTop(numberField),
townLabel.WithSameLeft(fNameLabel),
townLabel.WithSameRight(streetLabel),
townLabel.Below(numberField, vMargin),
townField.WithSameLeft(townLabel),
townField.WithSameWidth(townLabel),
townField.Below(townLabel, vMargin),
zipLabel.WithSameLeft(fNameLabel),
zipLabel.WithSameWidth(townLabel),
zipLabel.Below(townField, vMargin),
zipField.WithSameLeft(townLabel),
zipField.WithSameWidth(zipLabel),
zipField.Below(zipLabel, vMargin),
debug.WithSameLeft(townLabel),
debug.WithSameWidth(zipLabel),
debug.AtBottomOf(View, vMargin)
);
```
... although I think there are some opportunities to shorten that code and perhaps also to use some code-based hints too!
**Could I create a generic vertical scrolling StackPanel/LinearLayout?**
[![](http://4.bp.blogspot.com/-g59dSQE1JeA/Ue0qHXI-qSI/AAAAAAAAA-4/KF1QKPtcrTc/s320/constraints.png)](http://4.bp.blogspot.com/-g59dSQE1JeA/Ue0qHXI-qSI/AAAAAAAAA-4/KF1QKPtcrTc/s1600/constraints.png)
```
public static IEnumerable<FluentLayout>
VerticalStackPanelConstraints(
this UIView parentView,
Margins margins,
params UIView[] views)
{
margins = margins ?? new Margins();
UIView previous = null;
foreach (var view in views)
{
yield return view.Left()
.EqualTo()
.LeftOf(parentView)
.Plus(margins.Left);
yield return view.Width()
.EqualTo()
.WidthOf(parentView)
.Minus(margins.Right + margins.Left);
if (previous != null)
yield return view.Top()
.EqualTo()
.BottomOf(previous)
.Plus(margins.Top);
else
yield return view.Top()
.EqualTo()
.TopOf(parentView)
.Plus(margins.Top);
previous = view;
}
if (parentView is UIScrollView)
yield return previous.Bottom()
.EqualTo()
.BottomOf(parentView)
.Minus(margins.Bottom);
}
```
**Adaptive!**
One key thing to note about these constraint-based UIs is that they are **adaptive** - e.g. when you rotate the phone then the layout adapts:**
**
**&nbsp; **
[![](http://4.bp.blogspot.com/-QPYBmqKdOU0/Ue0sawecVPI/AAAAAAAAA_Q/SPJwrjTI9zs/s320/form2.png)](http://4.bp.blogspot.com/-QPYBmqKdOU0/Ue0sawecVPI/AAAAAAAAA_Q/SPJwrjTI9zs/s1600/form2.png)
[![](http://4.bp.blogspot.com/-cb6mOm4F-28/Ue0scetnOjI/AAAAAAAAA_Y/FXQg3PbCB8c/s320/stackpanelH.png)](http://4.bp.blogspot.com/-cb6mOm4F-28/Ue0scetnOjI/AAAAAAAAA_Y/FXQg3PbCB8c/s1600/stackpanelH.png)
**The code**
The code I created is currently sitting in [https://github.com/slodge/MvvmCross-Tutorials/tree/master/QuickLayout/Cirrious.FluentLayout](https://github.com/slodge/MvvmCross-Tutorials/tree/master/QuickLayout/Cirrious.FluentLayout) - along with a test MvvmCross project (one level up).
It may later move into an MvvmCross plugin - or into core MvvmCross - but for now it's just sitting there in Tutorials. License is Ms-PL as per normal.
**A video demo - laying out a tipcalc view**
<iframe width="420" height="315" src="//www.youtube.com/embed/5BAuOq-FcJM" frameborder="0" allowfullscreen></iframe>
**More?**
With all this said and done, whether or not you prefer declarative or [Imperative](http://blog.xamarin.com/creating-imperative-uis-in-c/) UI code is very much a matter of taste... but one question that I'm wondering at the moment is whether I could use the same UI code to create layouts in different environments - whether the same `AtTopOf`, `ToLeftOf` type calls could be used to generate UIKit, Xaml or Axml... but that question will have to wait for another day....
<div style='clear: both;'></div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment