Last active
August 23, 2020 09:52
-
-
Save praeclarum/d7dc5d83bacf84c127c4 to your computer and use it in GitHub Desktop.
EasyLayout makes writing auto layout code in Xamarin.iOS F# easier.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 && operator.</para> | |
/// <code><@ button.Frame.Left >= text.Frame.Right + 22 && | |
/// 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 |
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 -> ...
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?
awesome!
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
@VesaKarvonen Thats why I mentioned the custom symbol earlier on, that way you could use infix notation for the constraints still. You could also use a computation expression to hide the complexity of the combinators too.