# UIStackView magic

This component is one of the things that should´ve been there for ages, as it facilitates layout design considerably. In every UI environment, there is a basic need to stack elements horizontally or vertically, having them rearranged when any element is added or removed. This takes a second in other platforms however iOS auto-layout is sometimes a painful process for pretty simple tasks.

## What´s wrong with the good old, super awesome AutoLayout?

Consider the following layout (“–” represents padding):

`|--view1--view2--view3--|`

Doing this silly layout the “old” way involves lots of constraints, for instance:

* align the views vertically (2 or 3 constraints)
    
* set leading and trailing of each view (3 to 4 constraints)
    
* set with and height of each view (6 constraints or maybe 3 if they provide intrinsic height)
    

Next week your boss comes to tell you that view2 is no longer needed, so you have to:

* delete view2
    
* manually rearrange view3 to get the place where view2 was placed, fixing all the wrong constraints
    

Then, a great designer in your team tells you by Slack that paddings are too small. There´s no single “padding” or “margin” property. Again, you need to change all leading/trailing constraints of each view, one by one.

And well… we are talking about “design time” here (aka Interface Builder). If you need to do this on runtime (**state changes**) it may become a nightmare because you first need to locate the attached constraints to remove them and then create new ones, if you know what I mean.

Now imagine you´ve got like 5 more places in the screen where this can happen. The next move is probably pulling your hair and complaining about how miserable your life is.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698359207936/95b52d14-467a-439c-941c-d4f75115fba2.gif align="center")

## What can UIStackView do for me?

This guy is using AutoLayout internally and it does nothing you can´t do with AutoLayout and some code but it will make your life easier in many ways. Think of it as some kind of Android LinearLayout. It can also be nested easily to achieve more complex layouts.

Going back to the previous example, if view1, view2 and view3 were within a `UIViewStack` wouldn´t need any constraint created manually (unless you want to) and removing one of them (design time or runtime) will readjust all the constraints for you. **Done**. The same happens if you want to add a new view to the stack. You can even animate that change with a one-liner, as we will see later.

To recap:

1. Design time layouts become easier
    
2. Coded layouts become easier
    
3. Runtime state changes (adding or removing elements) take a single line of code.
    
4. Animating layout changes is now very simple
    

## Shut up and show me the code

No need to give you all the small details of this component, as you can easily [google it](https://www.google.es/search?q=uistackview+tutorial&oq=uistackview+tutorial&aqs=chrome..69i57j0l3j69i60.3412j0j4&sourceid=chrome&ie=UTF-8). Instead, I´m going to jump right to the code with some real cases I came up with.

All the samples will be coded (sorry IB fans) and layout with the anchor constraint API (did I say it correctly?) and a few [method extensions](https://github.com/xleon/UIStackViewPlayground/blob/master/Helpers/ConstraintUtil.cs) to speed up trivial tasks. If you don´t know what it is, go ahead and read my [previous post](http://xleon.net/xamarin/ios/nslayoutconstraint/autolayout/getting-fancy-with-uiview-anchors-and-state-changes.html).

## Adding and removing elements

For `UIStackView` to manage children's constraints properly, use any of the following methods:

```csharp
stackView.AddArrangedSubview(view);
stackView.InsertArrangedSubview(view, index);
stackView.RemoveArrangedSubview(view);
```

You can also remove a child view like you normally would:

`view.RemoveFromSuperview();`

## A vertical scroll of stacked elements

Let´s say you need a simple scroll for a small number of stacked elements (better use a Table if you need many) but you don´t need/want to mess with cells.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698366326501/b689e0dc-f266-4857-96ce-60539832351f.gif align="center")

A simple `UIStackView` within a `UIScrollView` will make the trick:

```csharp
public override void ViewDidLoad()
{
    base.ViewDidLoad();

    // Create and add Views

    var stackView = new UIStackView
    {
        Axis = UILayoutConstraintAxis.Vertical,
        // make the inner views to fill the whole available width
        Alignment = UIStackViewAlignment.Fill, 
        Distribution = UIStackViewDistribution.EqualSpacing,
        // margin between views
        Spacing = 4, 
        // apply padding between the parent view (the scroll) 
        // and the stack
        LayoutMargins = new UIEdgeInsets(4, 4, 4, 4),
        // this will make padding actually work
        LayoutMarginsRelativeArrangement = true 
    };

    var scroll = new UIScrollView();
    Add(scroll);

    scroll.AddSubview(stackView);

    // Add some views to the stack with random height and color
    // Notice that we use AddArrangedSubview rather than the 
    // former AddSubview for UIStackView to manage it. 
    // Otherwise it won´t work
    for (var i = 0; i < 20; i++)
        stackView.AddArrangedSubview(GetRandomView());

    // Layout Views

    // For AutoLayout to work, all views and nested views need to set 
    // "TranslatesAutoresizingMaskIntoConstraints" property to `false`. 
    // It´s easy to forget it so I created an extension method that 
    // will do this to the view and its subviews
    scroll.EnableAutoLayout();

    // "FullSizeOf" is a method extension to set leading, trailing, 
    // bottom and top constraints
    scroll.FullSizeOf(View); 
    stackView.FullSizeOf(scroll);

    // constraint needed when the UIStackView is inside the UIScrollView
    stackView.WidthAnchor
        .ConstraintEqualTo(scroll.WidthAnchor)
        .Active = true;
}

private static readonly Random Random 
    = new Random(DateTime.Now.Millisecond);

private static UIView GetRandomView()
{
    var view = new UIView();
    view.BackgroundColor = ColorUtil.GetRandomColor();
    view.Layer.CornerRadius = 8;

    var randomHeight = Random.Next(25, 200);
    view.HeightAnchor.ConstraintEqualTo(randomHeight).Active = true;
    return view;
}
```

I´m using here two custom method extensions: [FullSizeOf()](https://github.com/xleon/UIStackViewPlayground/blob/master/Helpers/ConstraintUtil.cs#L12) and [EnableAutoLayout](https://github.com/xleon/UIStackViewPlayground/blob/master/Helpers/ConstraintUtil.cs#L87) to make the code cleaner.

## Nested stacks

Nesting stacks is no mystery as it works like nesting any other view.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698366384620/d69ab020-1805-4916-8fb4-132938b3cf4c.gif align="center")

The layout hierarchy looks like the following:

```csharp
UIScrollView
    UIStackView
        UIView (with gray background)
            UILabel
            UIStackView
                Children
        UIView (with gray background)
            UILabel
            UIStackView
                Children
        ...
```

Grab the source code for this screen [here](https://github.com/xleon/UIStackViewPlayground/blob/master/Views/NestedStacksViewController.cs)

## Add/remove elements with animation

Animating constraints in iOS has been always simple:

```csharp
// add/remove/change constraints and then...
UIView.Animate(0.3, () => view.LayoutIfNeeded());
```

Working with `UIViewStack` is not different. When adding or removing a view from the stack it will internally change the constraints with AutoLayout so the animation works in the same way. Let´s create a button that will add an arranged subview with a random color to the stack. Then destroy that view when the user taps on it:

```csharp
public override void ViewDidLoad()
{
    base.ViewDidLoad();

    var stack = new UIStackView
    {
        TranslatesAutoresizingMaskIntoConstraints = false,
        Axis = UILayoutConstraintAxis.Vertical,
        Alignment = UIStackViewAlignment.Fill,
        Distribution = UIStackViewDistribution.FillEqually,
        Spacing = 4
    };

    Add(stack);

    stack.FullSizeOf(View);

    var addButton = new UIButton(UIButtonType.System);
    addButton.SetTitle("Add view", UIControlState.Normal);
    addButton.TouchUpInside += (s, e) =>
    {
        stack.InsertArrangedSubview(GetAutoDestroyButton(), 1);
        UIView.Animate(0.3, () => stack.LayoutIfNeeded());
    };
    
    stack.AddArrangedSubview(addButton);
}

private static UIButton GetAutoDestroyButton()
{
    var button = new UIButton
    {
        BackgroundColor = ColorUtil.GetRandomColor(true),
        ClipsToBounds = true
    };

    button.SetTitle("Destroy", UIControlState.Normal);
    button.TouchUpInside += (sender, args) => 
        UIView.Animate(0.3, () => 
            button.Hidden = true, button.RemoveFromSuperview);

    return button;
}
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698366517800/b7ded2ca-eaab-433e-b141-b1c3e85f8a34.gif align="center")

## Accordion component POC

In a real application, I would probably use a `UITableView` to create an accordion component because it will handle items and scroll more efficiently using cell virtualization. However, this can be done faster with a `UIStackView` and it works pretty decently for a few views:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698366639072/e41cdf56-6edc-4c1c-820b-bc5abfc6806c.gif align="center")

In this sample, only a single item can be opened at a time. The content view is a resized `UILabel` with a random height, but it could be anything else.

It´s worth saying that content views in this sample are all added on initialization and later hidden and shown. This means that `UIStackView` is **smart enough** to handle constraint changes not only when views are added or removed, but also **when their visibility changes** (`view.Hidden = true|false`), simplifying your code even more.

```csharp
private static readonly Random Random 
    = new Random(DateTime.Now.Millisecond);

private UIStackView _stackView;
private UIView _visibleContent;

public override void ViewDidLoad()
{
    base.ViewDidLoad();

    _stackView = new UIStackView
    {
        TranslatesAutoresizingMaskIntoConstraints = false,
        Axis = UILayoutConstraintAxis.Vertical,
        Alignment = UIStackViewAlignment.Fill,
        Distribution = UIStackViewDistribution.EqualSpacing,
        Spacing = 1
    };

    var scroll = new UIScrollView();
    Add(scroll);

    scroll.AddSubview(_stackView);
    scroll.FullSizeOf(View);
    scroll.EnableAutoLayout();

    _stackView.FullSizeOf(scroll);
    _stackView.WidthAnchor
        .ConstraintEqualTo(scroll.WidthAnchor)
        .Active = true;

    for (var i = 0; i < 25; i++)
    {
        _stackView.AddArrangedSubview(
            GetButton($"Category {i + 1}", i));

        _stackView.AddArrangedSubview(
            GetContent($"Child of category {i + 1}", i + 100));
    }
}

private UIButton GetButton(string title, int tag)
{
    var button = new UIButton
    {
        BackgroundColor = UIColor.Blue,
        HorizontalAlignment = UIControlContentHorizontalAlignment.Left,
        ContentEdgeInsets = new UIEdgeInsets(4, 10, 4, 10),
        ClipsToBounds = true,
        Tag = tag
    };

    button.SetTitle(title, UIControlState.Normal);

    button.TouchUpInside += (sender, args) =>
    {
        var content = View.ViewWithTag(((UIButton) sender).Tag + 100);
        UIView.Animate(0.3, () =>
        {
            if (_visibleContent != null)
                _visibleContent.Hidden = true;

            if(!Equals(_visibleContent, content))
                content.Hidden = !content.Hidden;

        }, () => _visibleContent = content.Hidden ? null : content);
    };

    return button;
}

private static UILabel GetContent(string title, int tag)
{
    var content = new UILabel
    {
        Text = title,
        Lines = 0,
        TextAlignment = UITextAlignment.Center,
        TextColor = UIColor.Black,
        BackgroundColor = UIColor.LightGray,
        Tag = tag,
        Hidden = true
    };

    content.HeightAnchor
        .ConstraintEqualTo(Random.Next(40, 250))
        .Active = true;

    return content;
}
```

## Change axis with animation

I don´t see any useful case to change the axis in a real app, but here it is. Changing from horizontal to vertical and viceversa takes one line:

```csharp
stackView.Axis = UILayoutConstraintAxis.Horizontal;
stackView.Axis = UILayoutConstraintAxis.Vertical;
```

And if you want to animate it:

```csharp
UIView.Animate(0.3, () =>
{
    stackView.Axis = stackView.Axis == UILayoutConstraintAxis.Vertical
        ? UILayoutConstraintAxis.Horizontal
        : UILayoutConstraintAxis.Vertical;

    stackView.LayoutIfNeeded();
});
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698366724838/54951e1b-8685-4708-b205-14991c4bf96c.gif align="center")

Enough for now, you can find these and other samples in the [GitHub repo](https://github.com/xleon/UIStackViewPlayground).  
Here it´s a screen recording with the rest of them:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1698366744061/a7737650-01c6-404a-8f6a-ccc5d83fcadc.gif align="center")

Remember that `UIStackView` is available from iOS 9. If you need to support previous versions, go ahead and try [Cheesebaron TZStackView](https://github.com/Cheesebaron/TZStackView).
