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 a number of NSLayoutAnchor properties

anchor properties


that can be used to create constraints (NSLayoutCostraint):

constraint methods


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 literally 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:

change state


Related Posts

How to use Charles Proxy in Xamarin to capture network traffic (including SSL)

Fixing push notifications on Android 8. Aka channels

Android 8 adaptative (vector) icons

UIStackView magic

When Apple realized a LinearLayout could be useful for developers...

A simple page-indicator for your android view-pager

A bit of xml and C#. No 3rd party libs required

Large file downloads on Windows 10 mobile

Download safely in the background with progress feedback

UWP mobile side loading

SQLite.NET > async VS sync

Or how we (developers) love to complicate our code base

Making iOS forms usable

Prevent user frustration by implementing good practices with Xamarin.iOS

Easy and cross-platform localization (Xamarin & .NET)

Share locales from a PCL. Get up and running in no time