Alignments

In this post I’ll discuss how alignments work in SwiftUI, building our understanding of how they function, and finishing by demonstrating how to create your own custom alignments for specific needs.

Let’s start by taking a simple view that lays out horizontally three images of different heights:

struct ContentView : View {
    var body: some View {
        HStack(alignment: .top) {
            Image("rogue")
            Image("dragoon")
            Image("brawler")
        }
    }
}

If you haven’t read my post about stacks, read that now, since there are two invariants about HStack that are worth ensuring we understand:

  • the width of the stack will be the sum of the widths of its children views, plus spacing
  • the height of the stack will be the height of its tallest child, plus positioning

This means that the only flexibility is in the vertical positioning of each child, and that’s where alignment comes in. Alignment positions views in the perpendicular axis of the stack: vertical alignment for a horizontal stack, and horizontal alignment for a vertical stack.

A common error is to seek to use alignment to change the position of a child view in the same axis as the stack. If we’ve understood the behavior of the stack, we’ll realize that the horizontal position of a child in a horizontal stack is simply determined by the widths of the previous children in that same stack. Since views have fixed sizes that we set, we don’t need alignment at all for that type of layout.

But in our example, it’s the vertical positioning of views laid out horizontally that we care about. We’ve used the .top alignment in the example, and as expected this aligns all of the views to the top of of the stack.

We know what we expected the result to be, and hopefully it’s not a surprise, but it’s worth stepping through the process of alignment with this simple example before we take on something more complex.

views aligned using .top

To show the positions and sizes of the views, I’ve used a red border for the images, a green border for the stack, and showed the position of alignment with a yellow line.

An alignment is a key to a set of values that a view can return, called alignment guides. Each of these guides is simply a value of length in the view’s own co-ordinate space, from its top-left corner. The default value of the .top alignment guide for a view is therefore simply 0.

The process of alignment within the stack is to convert those values from the child view’s co-ordinate space, into the stack’s, vertically repositioning either the new child or the stack’s existing children so that the position of the same alignment guide across all of its children views match.

The stack considers each child in turn. First the stack increases its own width by the width of the new child, plus spacing if required. This creates space on the trailing edge of the stack for the new child to be placed.

For vertical positioning the stack maintains and updates its own value for the alignment, in its own co-ordinate space, which begins as 0. The stack compares the relevant alignment guide of the new child with this value.

If the new child’s alignment guide value is greater, the vertical position of all existing children in the stack is increased by the difference in values, and the stack’s value of the alignment is updated from the new child’s alignment guide.

If the new child’s alignment guide value is lesser, the vertical position of the new child is set to the difference in value, and the stack’s value of the alignment remains the same.

Finally the stack adjusts its height to ensure all of its children are accommodated.

Since the stack’s alignment value begins as 0, and in the case of .top each child’s alignment guide value is 0, the different is always 0 so vertical positioning occurs, the stack’s alignment remains at 0, and each child’s resulting vertical position is 0.

Center Alignment

Since .top was the easy case as no vertical repositioning was necessary, now let’s instead consider the apparently more complex case of .center.

The code is fundamentally the same, all we change is the value of the alignment parameter to HStack:

struct ContentView : View {
    var body: some View {
        HStack(alignment: .center) {
            Image("rogue")
            Image("dragoon")
            Image("brawler")
        }
    }
}

The invariants for the stack remain the same, which means that all that can change in the layout is the vertical positioning of children. As we saw by stepping through the process, this is determined by the values for the alignment guides of those children.

It’s helpful at this point to have a reference to the values of the alignment guides for each of the images we’re using.

Image.top.bottom.center
rogue06432
dragoon09145
brawler07638

As we’d hopefully expected the value of the .top alignment guide is always 0 and the value of the .bottom alignment guide is simply the height of the image, since that’s the length in the view’s own co-ordinate space from the top-left corner to the bottom of the image.

The value of the .center alignment guide is derived from these two, and is the distance from the top-left to the position halfway between its .top and .bottom alignment guide. This is done automatically for us, but the code inside SwiftUI to do that is simple and worth a pause to consider:

// The complicated, but correct, definition.
d[.center] = d[.top] + (d[.bottom] - d[.top]) / 2

// If we assume .top is always zero.
d[.center] = d[.bottom] / 2

We don’t need to define this alignment ourselves, so let’s go back to our example above and see what happens when we use .center alignment:

views aligned using .center

The result is what we expected, but now that we’ve walked through the alignment process for .top, and seen the values of the alignment guides for .center, we can follow the same process and reason about what’s going on.

The stack begins with a zero size, and an alignment value of zero.

The rogue image is added as the first child, and the stack increases its width by the width of that image. Since the value of 32 for the image’s .center alignment is greater than the stack’s current value of 0, and there are no existing children to reposition, the image is positioned at 0 and the stack’s alignment value is simply set from the image and is now 32. Finally the stack adjusts its height to the height of that image

The dragoon image is added as the second child, which increases the stack’s width by both the width of that image and its own value for spacing. Next it compares the new image’s value of 45 for the .center alignment guide with its own current value of 32; since this is greater, the existing children are vertically repositioned by the difference, in this case: 13. The new image is positioned at 0, and the stack’s alignment value is set from the new image, which is now 45. Finally the new image’s height is 91 which is larger than the stack height of 64, so the stack increases its height to that of the new image.

The brawler image is added as the third and final child, again increasing the width of the stack by the image width plus spacing. The value of 38 for the new image’s .center alignment guide is compared to the stack’s current value or 45; since this is lesser, it’s the new image that is positioned vertically by the difference of 7. The vertical position of the existing children, and the stack’s alignment value, both remain unchanged. The height of this image is 76, which is less than the stack’s 91, so no change in the stack size occurs as there is enough room for the image.

So as we can see, despite being a more complicated result, the process of .center alignment is identical to that of .top alignment, just with different values for the relevant alignment guide.

Custom Alignments

Let’s take this further and see what it takes to create a completely custom alignment. Remember that an alignment only affects the position along the perpendicular axis of the stack.

For our example, we want to align our characters so that they appear to be standing on the same plane. We could try using the .bottom alignment but due to the differences in drawings, that doesn’t quite cut it.

views aligned using .bottom

This is an ideal situation for creating and using a custom alignment.

First let’s remember that an alignment isn’t anything special, it’s just a key to a set of values associated with a view. To create a custom alignment, we need to define a new key, and to use a custom alignment we need to provide a value for that key for views we wish to align with it.

Keys for vertical alignments, such as we use for a horizontal stack, are instances of the VerticalAlignment type. The .top, .center, and .bottom alignments we’ve used so far are static properties of the type that return an appropriate instance.

To define our own we add our own static property that returns an instance for our new alignment. To create the instance we need a type that confirms to the AlignmentID protocol, this acts as the identifier for the alignment, and provides a default value for the alignment guide of any view that doesn’t otherwise specify it.

Since we want to align our images based on their feet, we’ll call our new alignment .feet. As a default value the existing .bottom alignment works well enough.

extension VerticalAlignment {
    enum Feet: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> Length { d[.bottom] }
    }
    static let feet = VerticalAlignment(Feet.self)
}

We can now use `.feet` anywhere we would have used .bottom and it’ll have the same effect.

That’s not quite enough, we now need to use this alignment by changing the values of the .feet alignment guides for our images. Remember that these values are simply lengths, in the image’s own co-ordinate space, from its top-left corner; we don’t need anything complicated here, we can directly use the vertical pixel positions that we measured in Photoshop.

struct ContentView : View {
    var body: some View {
        HStack(alignment: .feet) {
            Image("rogue")
                .alignmentGuide(.feet) { _ in 61 }
            Image("dragoon")
                .alignmentGuide(.feet) { _ in 84 }
            Image("brawler")
                .alignmentGuide(.feet) { _ in 70 }
        }
    }
}

Compared to our previous examples, in this example we set alignment parameter on HStack to our custom .feet alignment, and for each of our images we added an .alignmentGuide(.feet) that returns a value for that alignment guide.

views aligned using .feet

No new magic is required to perform this layout. As the stack adds each of children, as before, it simply reads the alignment guide values we’ve provided to determine how to vertically position each child.

Adding the second image repositions the first by 23, since the value of 84 for its .feet alignment guide is greater than the 61 for the first.

When the third image is added, it’s vertically positioned at 14 rather than adjusting the existing images, since the value of 70 for its .feet alignment guide is lesser than the 84 of the second, which is the greatest seen so far.

Aside from the different values, this is the exact same process used for the .top and .center alignments we’ve seen so far.

Derived custom alignments

For our example the hardcoded pixel positions were sufficient, but the .alignmentGuide modifier closure receives a value of the ViewDimensions type.

We can use this to derive complicated alignment values in the same way that SwiftUI derives the value for .center from the .top and .bottom alignment guide.

The closure accepts a dictionary of the current set of alignment values, for this example we didn’t need that, but we can use that to make any combination of alignments we desire.

For example an alignment guide that is 75% of the distance between the .top and .bottom:

Image("cleric")
    .alignmentGuide(.custom) { d in d[.bottom] * 0.75 }

Or a vertical alignment guide that is the derived from the width of the image:

Image("wizard")
    .alignmentGuide(.square) { d in min(d.width, d.height) }

Keep in mind that while powerful, you’re still limited to only affecting the vertical position within a horizontal stack, and the horizontal position within a vertical stack.

Expanding a stack with custom alignments

So far our examples haven’t caused the tallest element to be repositioned, this was deliberate to allow the fundamentals of alignment to be understood, but it is possible.

As we know, when a new child is added with a value for the alignment guide that is less than the stack’s value, the new child is vertically positioned by the difference.

We’ve only considered alignment guide values within the bounds of the tallest image, what if the values are out of those bounds, or cause the tallest image to need to be vertically positioned?

If there is insufficient vertical space for the new child at this position, the stack is expanded in height to accommodate it.

Let’s demonstrate with some code that uses a custom alignment to align the three images in such a way that the tallest image needs to be vertically positioned:

extension VerticalAlignment {
    enum Custom: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> Length { d[VerticalAlignment.center] }
    }
    static let custom = VerticalAlignment(Custom.self)
} 

struct ContentView : View {
    var body: some View {
        HStack(alignment: .custom) {
            Image("rogue")
                .alignmentGuide(.custom) { d in d[.bottom] }
            Image("dragoon")
                .alignmentGuide(.custom) { d in d[.top] }
            Image("brawler")
                .alignmentGuide(.custom) { d in d[.center] }
        }
    }
}

The intent is that the bottom of the rogue is aligned with the top of the dragoon and the center of the brawler, and that’s what we get:

views aligned using .custom

This alignment meant that the horizontal stack had to be expanded vertically to accommodate the vertical position of the dragoon image, and as we can see from the green border, it was.

Let’s go back over the process once more to see that the process is the same, just with a more detailed explanation of one of the steps.

The stack begins with a zero size, and an alignment value of zero.

The rogue image is added as the first child. The value of our .custom alignment guide is that of the image’s .bottom alignment guide, which is 64; since this is greater than the stack’s current value of 0, and there are no existing children to reposition, the image is positioned at 0 and the stack’s alignment value is simply set from the image and is now 64. Finally the stack sets its height to the height of the new image.

The dragoon image is added as the second child. The value of the .custom alignment guide is that of the image’s .top, which is 0; that is less than the current alignment value for the stack so it’s the new image that is positioned vertically by the difference of 64. The vertical position of the existing child, and the stack’s alignment value, both remain unchanged. But now the stack needs to increase in height, not just to accommodate the 91 pixel tall image, but its position of 64 as well; the stack’s height becomes 155.

The brawler image is added as the third and final child. The value of the .custom alignment guide is that of the image’s .center, which is 38; this too is less than the current alignment value for the stack, so this new image positioned vertically by the difference of 26. The vertical position of the existing children, and the stack’s alignment value, both remain unchanged. The height of this image is 76, plus the position of 38, is still less than the stack’s height of 155, so no change in the stack size occurs as there is enough room for the image.

As you can see, the only real change we made to the description of the process was to consider both the height and vertical position of each child when expanding the stack, rather than just the height.

ZStack Alignment

This post has concentrated on alignment within a horizontal stack to demonstrate the fundamentals. Transitioning the concepts to a vertical stack simply replaces the vertical positioning of the horizontal stack to a horizontal position within the vertical stack. But what about the z-axis stack?

ZStack has slightly different invariants compared to the horizontal and vertical, review my post about stacks if these are a surprise:

  • the width of the z-axis stack will be the width of its widest child, plus positioning
  • the height of the z-axis stack will be the height of its tallest child, plus positioning

Children are overlaid over each other, and we have flexibility in both their horizontal and vertical positioning. Since we know we can also expand the size of the stack through positioning, we can expand the size of the z-axis stack through alignment in both axes.

For alignment a ZStack accepts an instance of an Alignment type, which is initialized with instances of HorizontalAlignment and VerticalAlignment for each axis. We can build on our knowledge of custom alignments to define our own .anchor alignments that we’ll use to layout our views:

extension HorizontalAlignment {
    enum Anchor: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> Length { d[HorizontalAlignment.center] }
    }
    static let anchor = HorizontalAlignment(Anchor.self)
} 

extension VerticalAlignment {
    enum Anchor: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> Length { d[VerticalAlignment.center] }
    }
    static let anchor = VerticalAlignment(Anchor.self)
} 

extension Alignment {
    static let anchor = Alignment(horizontal: .anchor, vertical: .anchor)
}

These default to the center, but allow us to move the anchor of any one of our views, in either dimension. So let’s take advantage of this by making a ZStack of our views with the rogue and brawler above the dragoon:

struct ContentView : View {
    var body: some View {
        ZStack(alignment: .anchor) {
            Image("rogue")
                .alignmentGuide(HorizontalAlignment.anchor) { d in d[.trailing] }
                .alignmentGuide(VerticalAlignment.anchor) { d in d[.bottom] }
            Image("dragoon")
                .alignmentGuide(VerticalAlignment.anchor) { d in d[.top] }
            Image("brawler")
                .alignmentGuide(HorizontalAlignment.anchor) { d in d[.leading] }
                .alignmentGuide(VerticalAlignment.anchor) { d in d[.bottom] }
        }
    }
}

The process of alignment of a z-axis stack is much the same as a horizontal, except it maintains two alignment values, and compares both. Ordinarily this results in the single same point on each view being aligned over top of each other, but through custom alignments, we can achieve custom layouts:

views aligned using .anchor

FB6645146: While this is the result we expect, unfortunately at least as of Beta 3, this is not the result we get; while the views are aligned as specified, the z-axis stack size remains that of the largest child rather than being adjusted based on the positions of its children. What we get currently is:

views aligned using .anchor in Beta 3

Since this sizing behavior is not consistent with HStack and VStack I believe it to be a bug, and have therefore described above what I believe to be the intended behavior, with the currently not-working parts crossed out.

Alignment Across Views

The examples thus far have shown alignment within a single stack view, but for more complicated layouts, we frequently use a nested hierarchy or multiple stacks. How do we align views using custom alignments through such?

Fortunately after layout of a stack is complete, the stack itself now has a value for our custom alignment guide, the value is simply that it used for aligning its children.

This means that the HStack views in our .feet example can be placed inside any stack that is also aligned by .feet.

In fact, I made all of the output samples in this post by just adding .border to the views, and taking a screenshot of the Xcode Preview. The line was added by creating a 1px high Rectangle in a ZStack around the aligned HStack.

The ZStack was then aligned using a vertical alignment of .feet, resulting in the two views being aligned together correctly.

struct ContentView : View {
    var body: some View {
        ZStack(alignment: Alignment(horizontal: .center, vertical: .feet)) {
            HStack(alignment: .feet) {
                Image("rogue")
                    .border(Color.red)
                    .alignmentGuide(.feet) { _ in 61 }
                Image("dragoon"
                    .border(Color.red)
                    .alignmentGuide(.feet) { _ in 84 }
                Image("brawler")
                    .border(Color.red)
                    .alignmentGuide(.feet) { _ in 70  }
            }
            .border(Color.green)

            Rectangle()
                .fill(Color.yellow)
                .frame(width: 200, height: 1)
        }
    }
} 

Note that the Rectangle for the line doesn’t specify a value for the .feet alignment guide, so the default from the alignment definition will be used—in this case, .bottom.


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

Stacks

The three most important layout tools in your SwiftUI toolbox are the three kinds of stacks: horizontal, vertical, and z-axis (yes, there really is no good adjective for this one).

These views are parents to multiple children, and in order to fully understand how these lay them out, you should first understand that views have fixed sizes.

Horizontal Stack

A horizontal stack is created using an HStack and a view builder that specifies its children:

struct ContentView : View {
    var body: some View {
        HStack {
            Image("barbarian")

            Text("Nogistune Takeshi")
                .font(.title)
        }
    }
}

It lays out its children sequentially from its leading edge to its trailing edge:

hstack

We can learn a lot by extending and constraining this simple example.

The first child is an Image which is fixed in size to the size of its source image. Of the two children, this is the tallest, and we can see that the stack itself has this same height; as we’ll confirm shortly, the height of the horizontal stack is the height of its largest child.

the height of the horizontal stack is the height of its largest child

The second child is a Text which has the fixed size needed to render its string in the chosen font. It’s placed horizontally in the stack after the image, with space between them. A default spacing is used, and this can be changed with the spacing parameter to the HStack.

Since the Text is not as tall as the Image, the stack vertically centers it. This is an entire topic in of itself, and you can read more in alignments. For now we’ll stick to the default centering behavior.

No spacing is provided before the first child, or after the last child, only between them; thus the width of the horizontal stack is the sum of the widths of each of its children, plus the spacing between them.

the width of the horizontal stack is the sum of the widths of each of its children, plus the spacing between them

We can demonstrate that the height of the stack is the height of its largest child, not just the first child, by changing the order of its children:

hstack with the children in reverse order

With that confirmed, let’s now see what happens if we add more items to the stack. We’ll add both an icon and text label for the character’s class:

hstack with additional children

The stack simply expands in width to accommodate its new children. Each new child is placed after the previous sibling, which means that the horizontal position of a child in a horizontal stack is controlled only by the widths of the previous children.

the horizontal position of a child in a horizontal stack is controlled only by the widths of the previous children

This is a pretty important detail to learn; if you want to change the vertical position of a child in a horizontal stack, you use alignment, but if you want to change its horizontal position, you instead need to change the sizes of its siblings.

We haven’t shown the layout outside of the stacks in any of these examples, let’s see what happens if the size of the parent is larger than the combined sizes of the stack’s children:

hstack in larger frame

The stack does not expand to fill the space of its parent. The children of the stack are fixed in size, and stacks are no different, stacks are fixed in size, derived from the sizes of their children

stacks are fixed in size, derived from the sizes of their children

We know that views having fixed sizes doesn’t mean that they can’t be flexible about that fixed size, and we saw in that post that Text can return different sizes by truncating, tightening, and wrapping its string.

When there is not enough space in the parent of the stack for both the Image and the Text, the Text can help out by reducing its fixed size:

hstack with truncated text

That seems to work great, the Text truncated its content to allow the stack to fit within its parent.

But what if we had more than one Text in our stack? To see what happens, let’s divide our single Text into two, one each for the character’s family name and given name respectively:

struct ContentView : View {
    var body: some View {
        HStack {
            Image("barbarian")

            Text("Nogistune")
                .font(.title)
            Text("Takeshi")
                .font(.title)       
        }
    }
}

The result might be a little surprising, we might have expected the first Text to take up most of the space, and the second to be extra-truncated, but the stack has divided the space up fairly between them and caused them both to truncate:

hstack with two truncated txt

When the stack lays out its children, like every view, it receives its available space from its parent. Since it knows how many children it has, and their spacing, it subtracts the necessary spacing from the available space, and then divides that equally amongst its children.

It then first offers this equal space to its children that are inflexible in size, we’ll dive more into this in flexible frames, for now it’s enough to know that’s the Image. Since this uses a lot less of the width than is available in its equal part, the stack can divide the remainder up between the rest of its children.

Both Text views then get an equal share of the remainder, and as a result truncate equally.

If we want a little more control, for example if we want the character’s family name to be less likely to be truncated than their given name, we can introduce an additional pass to the layout of a stack by increasing the layout priority of the children we want in the extra pass.

struct ContentView : View {
    var body: some View {
        HStack {
            Image("barbarian")

            Text("Nogistune")
                .font(.title)
                .layoutPriority(1)
            Text("Takeshi")
                .font(.title)       
        }
    }
}

After subtracting space for spacing, and for inflexible children, the stack offers the remaining space to those with a higher layout priority first:

hstack with layout priority truncating second text only

Vertical Stack

The vertical stack is very similar to the horizontal, it is defined with VStack:

struct ContentView : View {
    var body: some View {
        VStack {
            Image("barbarian")

            Text("Nogistune Takeshi")
                .font(.title)
        }
    }
}

Rather than laying out its children horizontally, as we’d expect, it instead lays out its children sequentially from its top edge to its bottom edge:

vstack

Just like the horizontal stack, since each child view is fixed in size, the vertical stack itself is also fixed in size, derived from the sizes of its children.

Since it’s arranging its children vertically, it’s the width of the vertical stack that is the width of its largest child. Smaller children are positioned horizontally within that space.

the width of the vertical stack is the width of its largest child

By adding more children we can confirm that the behavior matches the horizontal stack, just transposed in direction:

vstack with additional children

And again, transposed from the horizontal, the height of the vertical stack is the sum of the heights of each its children, plus the spacing between them.

the height of the horizontal stack is the sum of the heights of each of its children, plus the spacing between them

As with the horizontal, the vertical stack does not expand to fill its parent frame, it retains the fixed size derived from its children and is positioned by its parent if it’s too small.

When the parent is too small for the stack content, it follows the same approach as the horizontal stack, first allocating the bounds for spacing, and then the size of inflexible views. The stack in the following example is placed in a smaller frame, bordered in gray:

vstack with restricted frame

We can see that the stack shrank horizontally as before, with the Text being flexible enough about its fixed size that it was able to truncate the string so that the stack can fit the frame.

But in the vertical direction the stack has failed to fit the bounds of its parent. When we discussed this for the horizontal we referred to the Image as inflexible in size, and Text as flexible, and while that’s true for the horizontal size, Text is just as inflexible in the vertical since reducing the font size is not one of its options.

So what we see is the same behavior as when we place an image inside a too-small parent, it simply overflows its bounds. This confirms that stacks are just like other views, and have fixed sizes.

Z-Axis Stack

The third kind of stack is neither horizontal or vertical, but instead overlays its children on top of each other. It is defined using ZStack:

struct ContentView : View {
    var body: some View {
        ZStack {
            Image("barbarian")

            Text("Nogistune Takeshi")
                .font(.title)
        }
    }
}

The children are overlaid sequentially, with the first defined on the bottom and the last defined on top:

stack

Just like the horizontal and vertical stacks, each child view is fixed in size, and the z-axis stack itself is also fixed in size, derived from the sizes of its children.

The height of the stack is the height of its tallest child. Less tall children are positioned vertically within that space.

The width of the stack is the width of its widest child. Smaller children are positioned horizontally within that space.

While it might seem limited in usefulness, the z-axis stack can actually be one of the most powerful tools for combining views, and is one of the fundamentals for creating new controls, or overlaying shapes.

Z-axis stacks are also particularly effective when used with custom alignments.


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

Views Have Fixed Sizes

One of the first things presented about SwiftUI is that its views determine their own sizes, and that those sizes are fixed. This is such a simple statement that it’s easy to move on quickly to get to the good stuff.

This is a mistake, because it’s deceptive in its simplicity; this statement fundamentally changes everything you know about layout. Until you understand all of the repercussions of it, you’ll be constantly feeling like you’re fighting SwiftUI for even the simplest layouts.

In this post we’ll look at two views that form the basic building blocks of many layouts, and what this assertion means for them: Image, and Text.

Images

Let’s start by placing an Image in our layout:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
    }
}

The specific image we’ve chosen has a size of 101 × 92 pixels, and we might be slightly surprised that the resulting view has the same size, and is centered within the layout of our device, instead of filling it.

an image

We might try using the .frame modifier on the view to change its frame:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .frame(width: 200, height: 200)
    }
}

But this doesn’t work either:

an image inside a frame

Rather than change the size of the image, it’s simply centered within the frame we specified. In fact, .frame does not change the view it modifies; it creates a new view with the specified size, and positions the view it’s modifying as a child inside it.

.frame does not change the view it modifies; it creates a new view with the specified size, and positions the view it’s modifying as a child inside it.

That’s worth repeating because it’s a fundamental of SwiftUI: you cannot directly change the size of a view. .frame, like all modifiers, actually returns a new view containing the modified view as a child.

The size of an image is fixed, even if you try and place it inside a frame that’s too small for it:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .frame(width: 50, height: 200)
    }
}

We might expect it to be resized in this case, even if not the other, but in fact it simply overflows its bounds:

an image overflowing a frame

The choice of overflowing by default instead of clipping can be customized through .clipped, however inspection will reveal that the Image still has its fixed size, and that this modifier simply affects drawing. Since this post is about layout, we won’t dwell on drawing modifiers.

We’ll come back to images again, but we need to learn more first, so we’ll take a look at Text instead.

Text

Let’s replace the Image in our layout with some Text instead, perhaps the name of our barbarian character:

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
    }
}
a text

Text too has a fixed size, which is simply the size necessary to render the string given to it.

We might try seeing how this looks inside a .frame:

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .frame(width: 200, height: 200)
    }
}

Knowing that a .frame creates a new view that contains the Text, we we should not be surprised that the size of the Text remains the same as before, and that it’s simply centered within the frame:

a text inside a frame

Unlike Image we actually do have a simple way of changing the fixed size of Text, albeit indirectly, by specifying the .font that we want to use.

It’s our hero’s name, so let’s use a .title font:

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
    }
}

The view will still have a fixed size, but since it’s using a larger font, we’d expect it to have a larger fixed size than it had before:

a text with a title font

This demonstrates that a view’s fixed size is chosen by the view itself, and that as a developer we can change that as long as we’re using modifiers that adjust environment that the view uses, such as .font.

a view’s fixed size is chosen by the view itself

So to complete the loop, let’s place our larger title-font Text inside a frame that would be too small for it:

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
            .frame(width: 200, height: 200)
    }
}

Given that we know views have fixed sizes, and from what we saw with similar code using Image, we should be expecting the Text to simply overflow its bounds just like the Image did:

But it didn’t!

This doesn’t contradict what we’ve just learned, it actually demonstrates another key principle we haven’t learned yet: a view’s fixed size is chosen by the view itself, but a view receives the size of its parent before deciding its own size.

a view receives the size of its parent before deciding its own size

Advanced Text Layout

Understanding what happened in the previous example, and the process by which the views were sized, and positioned, is key to understanding the flexible nature of SwiftUI’s fixed size layouts.

The Text view is a fixed size once laid out, but is flexible about what it can return as that fixed sized, depending on the size of its parent, in this case our 200 × 200 frame.

When the frame positions the Text inside it, it first informs the Text that its own size is 200 × 200, and only then asks the Text how large it is. This means that the Text can decide a different answer depending on the size of its parent.

In this case, the width available is insufficient to render the entire string, and Text is able to truncate it to fit. Once it has determined the length of the truncated string, plus ellipsis, it then returns the size necessary to render that as its own size.

This size is obviously smaller in height than the frame, but is actually often also smaller in width too. As you can see in the example, it did not simply return the parent width as its own width, it returned the fixed width necessary for the truncated string, and it was still necessary for the frame to center the text within it.

Truncation is not Text‘s only trick for returning a different fixed size, it can also tighten by compressing space between characters, and it can decrease its width by increasing its height, rendering the string across multiple lines:

struct ContentView : View {
    var body: some View {
        Text("Nogitsune Takeshi")
            .font(.title)
            .lineLimit(2)
            .frame(width: 200, height: 200)
    }
}

Here we’ve added a .lineLimit modifier increasing the default line limit from 1 to a new value of 2, this allows Text another approach for returning a different fixed size:

a multi-line text in a frame

Since the Text wasn’t able to fit the full width of the string in the space available, instead of truncating, the Text was now able to increase its height instead and return a fixed size larger in height than before.

Note still that the width of the Text is only enough to render the longest line, and the height is only enough to render both lines. Also note that while the Text is centered within the frame, the Text itself is responsible for its own multi-line alignment inside its own bounds.

Resizable Images

Now that we’ve learned that a view being a fixed size doesn’t mean the view can’t be flexible about what that fixed size is, we can revisit Image.

By default images have the fixed size of their source, but they have the option to be resizable:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .resizable()
            .frame(width: 200, height: 150)
    }
}

A resizable image simply returns the size of its parent as its own size, entirely filling any available space:

a resizable image

By default the image will fill the space by stretching the single source image, but by using .resizable(resizingMode: .tile) it would have repeated the source image:

a tiled resizable image

Note that the size of the Image is still the size of the frame, it’s simply rendering the source image multiple times within its bounds.

In many cases neither of those behaviors are what we want, and instead we want the image to fill as much of the parent as possible, while retaining the aspect ratio of its source. Fortunately there’s a modifier to specify that too:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 200, height: 150)
    }
}

Note that we still need the .resizable modifier, and that the ordering of the modifiers matters since it’s the resizable image we wish to constrain the aspect ratio of, not the frame:

a resizable image with an aspect ratio in a frame

The Image has chosen to use all of the parent’s available height, scaling the source up to fit, but has chosen a width that retains the aspect ratio of the original source. The Image therefore no longer has the full size of its parent, and the parent centers it.

With these rules firmly in our minds, we can now look at combining multiple views in our layouts by using stacks, or look at the interesting case of flexible frames.


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

Xcode: two Apps with a shared private Framework, in a Workspace

I’ve been working on a reasonably large iOS project that has ended up consisting of multiple, related, iOS apps for which I’ve wanted to share a large amount of code through the use of shared, private, frameworks. This seems like it should be simple, but instead I’ve run into all kinds of issues.

When building, you might end up with a warning about a missing directory:

ld: warning: directory not found for option '-F…/build/Debug-iphoneos'/

When trying to start the app in the iOS Simulator, without Xcode attached, it might crash with the error image not found:

Dyld Error Message:
  Library not loaded: @rpath/….framework/…
  Referenced from: …/Library/Developer/CoreSimulator/Devices/…
  Reason: image not found

If you try and run the app on a iOS device at all, whether with Xcode attached or not, you might also end up with an image not found error:

dyld: Library not loaded: @rpath/….framework/…
  Referenced from: /var/mobile/Containers/Bundle/Application/…/….app/…
  Reason: image not found

And most infuriating of all, once you come to create an archive of your app and attempt to validate it for TestFlight or the iOS App Store, you might end up with an error about missing BCSymbolMap files:

An error occurred during validation
The archive did not contain <DVTFilePath:…:‘…/Library/Developer/Xcode/Archives/….xcarchive/BCSymbolMaps/….bcsymbolmap’> as expected.

Searching the Internet has yielded increasingly complicated, and convoluted solutions up to, and including, custom build scripts. None of these were palatable, and after some experimentation, I believe I’ve found the way to make this work perfectly!

First you’ll want to create a new workspace to keep everything together. Use File → New → Workspace…, create a New Folder for your workspace to live in, and pick a name for the workspace. This will give you a new Xcode window, which unlike a usual project, contains no targets or files.

Now we’ll create the project for the first iOS app. With the workspace open, use File → New → Project…, pick the template, and enter the product name, etc. as usual. When you come to choose the location, make sure that the Add to: box has the workspace you selected.

This will return you to your workspace, but now with your new project added to it. Do it again for the second project, and be doubly careful at the last screen. The Add to: box will default to the workspace, but now there’s a new Group: box, and that defaults to the project you just created, not the workspace, so make sure you change it!

Now we can add the framework for the shared code. Once again use File → New → Project…, and this time pick the Cocoa Touch Framework project type.

Give your framework a name on the next screen, and again choose a location within your workspace folder, with the Add to: box containing the workspace, and changing the Group: box from the last project, to the workspace:

Now you have a workspace containing your two applications, and a framework for the code that you want them to share. The next bit is the tricky bit; we need both apps to depend on the framework, and while there are a number of ways of doing this, only one seems to work consistently for me without running into any of the warnings or errors above.

First you’ll want to reveal the Products of the framework you just added, and note that the final framework product is red, indicating that it doesn’t yet exist.

This is the first thing we want to fix, if we try and set things up without it being built, Xcode sometimes does the wrong thing! From the Scheme menu, select the framework itself, and from the Destination menu, select Generic iOS Device. Hit ⌘B or Product → Build to build the framework itself, and note that it turns black in the files list.

It’s safe to add it to the app. Select your first iOS app target, make sure the General tab is selected, and scroll until you can see Embedded BinariesDrag the framework from the left hand list, and drop it into this section. I’ve found that dragging, rather than using the “+” button; and using the Embedded Binaries section, rather than Linked Frameworks and Libraries one, is key to making Xcode work.

This will not just add it as an embedded binary, it will also add it to the Linked Frameworks and Libraries section as well, and will create a copy of the framework under the app project in the left hand list.

We’re not quite there… one more thing to do, select that copy of the framework from the files list for the app project—not the framework project—and make sure you can see the File Inspector. Note that the Location currently has Absolute Path, you need to change this to Relative to Build Products:

Do the same sequence for your second app.

By following these steps:

  • the App depends on the Framework, so builds with the latest version of the Framework source each time;
  • the iOS Simulator correctly embeds the Debug-iphonesimulator version of the Framework,
  • which means that the app will run both when attached to Xcode, and when not attached;
  • when built for the iOS Device, it correctly embeds the Debug-iphoneos version of the Framework,
  • so the app runs on the device both when attached to Xcode, and when not attached;
  • and the correct target versions are built and embedded when archiving,
  • which means you can validate and upload with both app symbols and bitcode.