Skip to content

Stacks (SwiftUI)


Prepared and tested with Xcode 13.3.1 and Swift 5.3 (last update: 2022-05-16)

In this part you will learn about the Stack container views included with SwiftUI and explain how you can use then to create user interface laying out all the componenrs according to your needs.

Table of contents


Stacks

As you saw in previous part, SwiftUI includes a wide range of user interface components you can use to develop your app such as labels, buttons or sliders.

The true art of making user interface is a matter of selecting the right interface components, deciding how they will be positioned on the screen, and planning resonable navigation between all screens and views of your app. All good framework provides a set of components dedicated to control how other components are laid out. With them you define some kind of rules how the user interface is organized and the way in which the layout responds to changes in screen orientation and size.

SwiftUI includes three stack layout views in the form of VStack (for vertical laying out), HStack (for horizontal) and ZStack (controling how views are layered on top of each other).

VStack and HStack behaves very similar way and differs only in orientation – you saw their usage in previous part related to user interface controls. ZStack controls "depth" – you can put controls or elements on the top of other user interface components.

Every stack is declared by embedding child views into a stack view:

To embed an existing component into a stack, either wrap it manually within a stack declaration as you did above, or hover the mouse pointer over the component in the editor so that it highlights, hold down the Command key on the keyboard and left-click on the component and from the resulting popup menu select the appropriate option:

You can realize more complex layouts simply by embedding stacks within other stacks, for example:

It's not bad however far from perfection. Notice for example what will happend if you replace

with

This happen because every VStack is independent from each other, so there is no row height synchronization. As you can see your layout needs some additional work, particularly in terms of alignment and spacing. You can do this using a combination of the spacer component, alignment settings and the padding modifier.


Alignment, spacer and padding


Alignment

Alignment is super easy and it's hard to say something really new in this context. Simply watch the examples below:


Spacer

To move the elements of your user interface apart, you need to add space between them. This space shouldn't be fixed but rather flexibly expand and contract along the axis of the containing stack (either horizontally or vertically) to provide a separating area between views. For this purpose, in SwiftUI you use the Spacer component.

SwiftUI’s Spacer view automatically fill up all available space on their axis of expansion, which means that it take up as much space as it can either horizontally or vertically, depending on what you put them in:

If you replace:

with:

you will get:


Fixed size spacer

As you may notice Spacer divide available space into equidistant separating area. In most cases this is what you want but sometimes you may keep more control over this process. You can do sophisticated calculations which is out of this material scope. The simples solution is to modify Spacer with frame.

Frame defined above is very restrictive – it must have height of 50 points. Better idea is to give system some flexibility to take up as much space as it can, however restricted to some extremal values: minimum i maximum. You can do this for example using .frame(minHeight: 50, maxHeight: 500) or specifying some constraints regardless of direction with for example Spacer(minLength: 50).


Padding

Another option to add spacing around the sides of any view is by using the padding() modifier. When called without a parameter system automatically use the "best" padding for the layout, content and screen size.

You may also pass a specific amount of padding as a parameter to the modifier:


Group view

Creating a user interface one day you may be surprised by a message

and your code will not compile:

However strange it may sound, the reason is very simple to explain. All container views in SwiftUI are limited to 10 direct descendant views. If a stack contains more than 10 direct children, the views will need to be sprad among multiple containers – you will have to add more grouping intermediate components for example by adding stacks as subviews. Don't affraid, performance of your app will not be affected by increasing the deepth of hierarchy.

Container deicated to this purpose is the Group view:

A you can see Group doesn't introduce any disturbance in components layout.


Layout priority

By default, an HStack attempt to display the text within its Text view children on a single line. If there is insufficient room the text will automatically wrap onto multiple lines when necessary if it is not restricted by the lineCount() modifier:

If modifier is applied, the stack view will decide how to truncate the text based on the available space and the length of the views:

You can send to the stack view informations about your preferences – you as a user interfce designer have a better understanding which text can be truncated and which should stay unaffected as long as possible. You do this by adding layoutPriority() modifier to the views in the stack and passing values indicating the level of priority for the corresponding view. The higher the number, the greater the layout priority and the less the view will be subjected to truncation.


ZStack view

ZStack adds depth to your layout. With this container you can place component on the top of other components.

One of the simplest usage example of ZStack is to overlap a text label on top of an image: