Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
EasyLayout makes writing auto layout code in Xamarin.iOS F# easier.
module EasyLayout
open System
open System.Drawing
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns
open MonoTouch.Foundation
open MonoTouch.UIKit
module private Utilities =
let rec eval e =
match e with
| FieldGet (Some o, f) -> f.GetValue (eval o)
| FieldGet (None, f) -> f.GetValue (null)
| PropertyGet (None, p, i) -> p.GetValue (null, i |> Seq.map eval |> Seq.toArray)
| PropertyGet (Some o, p, i) -> p.GetValue (eval o, i |> Seq.map eval |> Seq.toArray)
| Value (x, _) -> x
| _ -> raise (Exception (sprintf "Don't know how to eval %A" e))
let toAttr m =
match m with
| "X" | "Left" -> NSLayoutAttribute.Left
| "Y" | "Top" -> NSLayoutAttribute.Top
| "Width" -> NSLayoutAttribute.Width
| "Height" -> NSLayoutAttribute.Height
| "Bottom" -> NSLayoutAttribute.Bottom
| "Right" -> NSLayoutAttribute.Right
| "CenterX" | "RectangleF.get_CenterX" -> NSLayoutAttribute.CenterX
| "CenterY" | "RectangleF.get_CenterY" -> NSLayoutAttribute.CenterY
| "Baseline" | "RectangleF.get_Baseline" -> NSLayoutAttribute.Baseline
| _ -> NSLayoutAttribute.NoAttribute
let isConstrainableProperty m = toAttr m <> NSLayoutAttribute.NoAttribute
let (|GetFrameProp|) e =
match e with
| Let (_, PropertyGet (Some o, fn, _), PropertyGet (_, pn, _))
when fn.Name = "Frame" && isConstrainableProperty pn.Name ->
Some (eval o :?> NSObject, toAttr pn.Name)
| Call (_, pn, [PropertyGet (Some o, fn, _)])
when fn.Name = "Frame" && isConstrainableProperty pn.Name ->
Some (eval o :?> NSObject, toAttr pn.Name)
| _ -> None
let compileLeftSide side =
match side with
| GetFrameProp (Some x) -> x
| _ -> raise (Exception (sprintf "Left hand side of constraint is expected to be a UIView.Frame property. It was: %A" side))
let (|Mul|) side =
match side with
| GetFrameProp (Some (x, p)) -> Some (x, p, 1.0f)
| Call (_, m, [l; GetFrameProp (Some (x, p))]) when m.Name = "op_Multiply" -> Some (x, p, Convert.ToSingle (eval l))
| Call (_, m, [GetFrameProp (Some (x, p)); l]) when m.Name = "op_Multiply" -> Some (x, p, Convert.ToSingle (eval l))
| _ -> None
let compileRightSide side =
match side with
| Mul (Some x) -> (Some x, 0.0f)
| Call (_, mem, [Mul (Some x); c]) when mem.Name = "op_Addition" -> (Some x, Convert.ToSingle (eval c))
| Call (_, mem, [Mul (Some x); c]) when mem.Name = "op_Subtraction" -> (Some x, -Convert.ToSingle (eval c))
| Value (x, _) -> (None, Convert.ToSingle (x))
| FieldGet _ -> (None, Convert.ToSingle (eval side))
| _ -> raise (Exception (sprintf "Unrecognized right hand side: %A." side))
let compileConstraint left right rel =
let (firstObj, firstAttr) = compileLeftSide left
let (maybeObj, add) = compileRightSide right
match maybeObj with
| None -> NSLayoutConstraint.Create (firstObj, firstAttr, rel, null, NSLayoutAttribute.NoAttribute, 0.0f, add)
| Some (secObj, secAttr, mul) -> NSLayoutConstraint.Create (firstObj, firstAttr, rel, secObj, secAttr, mul, add)
let toRel m =
match m with
| "op_Equality" -> Some NSLayoutRelation.Equal
| "op_LessThanOrEqual" -> Some NSLayoutRelation.LessThanOrEqual
| "op_GreaterThanOrEqual" -> Some NSLayoutRelation.GreaterThanOrEqual
| _ -> None
let rec compileConstraints expr =
match expr with
| NewArray (_, es) -> es |> Seq.collect compileConstraints |> Seq.toList
| IfThenElse (i, t, e) -> compileConstraints i @ compileConstraints t @ compileConstraints e
| Call (_, m, [l; r]) when (toRel m.Name).IsSome -> [compileConstraint l r (toRel m.Name).Value]
| Value _ -> []
| _ -> raise (Exception (sprintf "Unable to recognize constraints in expression: %A" expr))
type RectangleF with
member this.CenterX = this.X + this.Width / 2.0f
member this.CenterY = this.Y + this.Height / 2.0f
member this.Baseline = 0.0f
type UIView with
/// <summary>
/// <para>Constrains the layout of subviews according to equations and
/// inequalities specified in <paramref name="constraints"/>. Issue
/// multiple constraints per call using the &amp;&amp; operator.</para>
/// <code><@ button.Frame.Left &gt;= text.Frame.Right + 22 &amp;&amp;
/// button.Frame.Width = View.Frame.Width * 0.42f @></code>
/// </summary>
/// <param name="constraints">Constraint equations and inequalities.</param>
member this.ConstrainLayout (constraints) =
let cs = Utilities.compileConstraints constraints |> Seq.toArray
this.AddConstraints (cs)
for x in cs do
(x.FirstItem :?> UIView).TranslatesAutoresizingMaskIntoConstraints <- false
cs
@vasily-kirichenko
Copy link

I think it's better to rewrite

let (|GetFrameProp|) e = ...

match side with
| GetFrameProp (Some x) -> ...

with

let (|GetFrameProp|_|) e = ...

match side with
| GetFrameProp x -> ...

@bentayloruk
Copy link

Thanks for building this. I'm keen to keep my auto-layout code tight!

I'm trying EasyLayout and it works fine on the simulator. However, I am getting an exception when debugging on a real iPhone. The code that is crashing is as follows:

override __.ViewDidLoad() =
    base.ViewDidLoad()

    let rootView = __.View
    rootView.BackgroundColor <- UIColor.LightGray

    // Create a sub content view.
    let contentView = new UIView(BackgroundColor = UIColor.LightGray)
    rootView.AddSubview(contentView)
    rootView.ConstrainLayout(
        <@[| contentView.Frame.Top = rootView.Frame.Top + 45.0f
             contentView.Frame.Left = rootView.Frame.Left
             contentView.Frame.Right = rootView.Frame.Right
             contentView.Frame.Bottom = rootView.Frame.Bottom |]@>) |> ignore

The exception in questions is as follows:

Failed to bind property 'Frame'. Parameter name: propName.

Anybody experienced this?

@robkuz
Copy link

robkuz commented Dec 3, 2014

awesome!

@hussam
Copy link

hussam commented Mar 26, 2015

Awesome Stuff! I updated it to work with the Unified API: https://gist.github.com/hussamal/0127e676a789fc704d8e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment