Secondary Views

Secondary views are one of the more interesting layout tools available in SwiftUI, to understand them first we have to recall that views have fixed sizes. To recap the process:

  1. Parent proposes a size to its child.
  2. Child decides on its size.
  3. Parent positions the child within its bounds.

Secondary views are useful because of where they fit in to this process, and how they interact with it. To demonstrate, let’s use a simple example:

struct ContentView : View {
    var body: some View {
        Text("Hasty River")
            .font(.title)
            .background(Color.yellow)
    }
}

We create a Text which will have a fixed size of its content, and then we add a secondary view using .background; the value of this is the secondary view added, in this case, a Color.

Color when used as a View simply sets its size to the proposed size received from its parent, fillings its bounds.

text with a yellow background

The result shows us that the proposed size for the secondary view is the fixed size decided on by the view its attached to.

the proposed size for the secondary view is the fixed size decided on by the view its attached to

So we can refine our process a little:

  1. Parent proposes a size to its child.
  2. Child decides on its size.
  3. Child proposes its size to its secondary view(s).
  4. Secondary view decides on its size.
  5. Parent positions the child within its bounds.

In our first experiment we just filled the secondary view with a color, what if we use a view there that’s inflexible about its size, and ends up being larger than the child? Perhaps an Image:

struct ContentView : View {
    var body: some View {
        Text("Hasty River")
            .font(.title)
            .background(Image("rogue"))
    }
}

If we’ve been paying attention we’re almost certainly going to expect that to break out of its bounds, but how does that affect the frame of the child its attached to?

text with background image overflowing bounds

The answer is that it doesn’t, the frame of the Text in green remains unaffected by the frame of the secondary view in red. All that happens is that the child positions its secondary view, even though it overflowed.

So now our process looks like:

  1. Parent proposes a size to its child.
  2. Child decides on its size.
  3. Child proposes its size to its secondary view(s).
  4. Secondary view(s) decides on their size.
  5. Child positions its secondary view(s) within its bounds.
  6. Parent positions the child within its bounds.

To see how this interacts with other views, let’s do a side-experiment using a VStack and some other lines of text:

struct ContentView : View {
    var body: some View {
        VStack {
            Text("My Character")
                .font(.caption)
            Text("Hasty River")
                .font(.title)
                .background(Image("rogue"))
            Text("Rogue")
        }
    }
}

If the secondary view has any part to play in the layout, we would expect to see the vertical stack account for it:

vstack of text with a background image on the middle text

The vertical stack ignored the secondary view completely; indeed everything we’ve learned about stacks should mean this isn’t a surprise.

We saw above that the Text did not change its size to account for the overflowing secondary view, so there was no way for the stack to account for it; after a view positions its secondary views they are otherwise completely removed from the layout process.

after a view positions its secondary views they are otherwise completely removed from the layout process

So a secondary view gives us two things:

  • a view that has a proposed size that is the decided size of the view it is attached to.
  • a view that is otherwise removed from the layout process.

The latter has the most utility in creating background views using .background, or overlay views using .overlay, that might be larger than their parent.

The former though can be extraordinarily useful in custom controls, we’ll look at making one in secondary views in practice.


Imagery used in previews by Kaiseto, original images and derived here licensed under Creative Commons 3.0 BY-NC-SA.