Getting fancy with UIView anchors and state changes

Getting fancy with UIView anchors and state changes

If you prefer coded user interfaces rather than designers (Xcode Interface Builder or Xamarin designers) you will surely like this API (available from iOS9+). It´s very readable, easy to maintain and feels more natural compared to the old one.

Every UIView has now several NSLayoutAnchor properties

that can be used to create constraints (NSLayoutCostraint):

Creating a constraint does not mean it will be active by default and you´ve got do it explicitly:

var c = origin.CenterXAnchor.ConstraintEqualTo(target.CenterXAnchor);
c.Active = true;

// or just
origin.CenterXAnchor
    .ConstraintEqualTo(target.CenterXAnchor)
    .Active = true;

// activate multiple constraints at once:
NSLayoutConstraint.ActivateConstraints(new[]{
    origin.CenterXAnchor.ConstraintEqualTo(target.CenterXAnchor),
    origin.CenterYAnchor.ConstraintEqualTo(target.CenterYAnchor)
});

It´s really easy to create UIView method extensions. For instance, to center a view in its parent (following the above sample):

public static NSLayoutConstraint[] CenterIn(this UIView origin, 
    UIView target, bool activate = true)
{
    origin.TranslatesAutoresizingMaskIntoConstraints = false;
    var contraints = new[]
    {
        origin.CenterXAnchor.ConstraintEqualTo(target.CenterXAnchor),
        origin.CenterYAnchor.ConstraintEqualTo(target.CenterYAnchor)
    };

    if (activate)
        NSLayoutConstraint.ActivateConstraints(contraints);

    return contraints;
}

// use case
view.CenterIn(parentView);

Now let´s adjust a view to its parent bounds:

public static NSLayoutConstraint[] FullSizeOf(this UIView origin, 
    UIView target, UIEdgeInsets edges, bool activate = true)
{
    origin.TranslatesAutoresizingMaskIntoConstraints = false;
    var contraints = new[]
    {
        origin.LeadingAnchor.ConstraintEqualTo(target.LeadingAnchor, edges.Left),
        origin.TrailingAnchor.ConstraintEqualTo(target.TrailingAnchor, -edges.Right),
        origin.TopAnchor.ConstraintEqualTo(target.TopAnchor, edges.Top),
        origin.BottomAnchor.ConstraintEqualTo(target.BottomAnchor, -edges.Bottom)
    };

    if(activate)
        NSLayoutConstraint.ActivateConstraints(contraints);

    return contraints;
}

public static NSLayoutConstraint[] FullSizeOf(this UIView origin, 
    UIView target, float margin = 0, bool activate = true) 
    => origin.FullSizeOf(target, new UIEdgeInsets(margin, margin, margin, margin), activate);

// use cases
view.FullSizeOf(parentView);
view.FullSizeOf(parentView, 10); // 10 points of margin
view.FullSizeOf(parentView, new UIEdgeInsets(5, 10, 5, 10));

And now let´s make a shortcut to set the width and height:

public static NSLayoutConstraint[] ConstraintSize(this UIView origin, 
    float width, float height, bool activate = true)
{
    origin.TranslatesAutoresizingMaskIntoConstraints = false;
    var contraints = new[]
    {
        origin.WidthAnchor.ConstraintEqualTo(width),
        origin.HeightAnchor.ConstraintEqualTo(height)
    };

    if (activate)
        NSLayoutConstraint.ActivateConstraints(contraints);

    return contraints;
}

// use case
view.ConstraintSize(100, 100);

Consider playing around with all anchors and Constraint...To() methods. You can make anything.

Those method extensions above return an array of constraints so we can deactivate/activate them later. Consider a set of constraints like a visual state we can store in a variable:

var small = view.ConstraintSize(100, 80);
var big = view.ConstraintSize(200, 200, false);

States can get even more complex by chaining the constraints from multiple objects:

var small = view1.ConstraintSize(100, 100)
    .Concat(view2.ConstraintSize(50, 50))
    .Concat(view3.ConstraintSize(30, 30))
    .ToArray();

var big = view1.ConstraintSize(200, 200, false)
    .Concat(view2.ConstraintSize(100, 100, false))
    .Concat(view3.ConstraintSize(60, 60, false))
    .ToArray();

State changes

Simply deactivate the old constraints and activate the new ones:

NSLayoutConstraint.DeactivateConstraints(small);
NSLayoutConstraint.ActivateConstraints(big);

With the basic blocks in place, let´s create a new extension to get fancy with state changes:

public static void ChangeState(this UIView parentView, 
    NSLayoutConstraint[] before, 
    NSLayoutConstraint[] after, 
    double duration = 0)
{
    parentView.LayoutIfNeeded();

    NSLayoutConstraint.DeactivateConstraints(before);
    NSLayoutConstraint.ActivateConstraints(after);

    if (duration > 0)
    {
        UIView.Animate(duration, parentView.LayoutIfNeeded);
    }
}

// use cases
View.ChangeState(small, big);
View.ChangeState(big, small, duration:0.3);

Here´s a working example: