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 choose their own 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 chooses its size to be the size of its source image. The second child is a Text which has chosen its size as that needed to render its string in the chosen font.

The Text is placed horizontally in the stack after the image, with space between them. We can specify the amount of space we want with the optional spacing parameter to the HStack, or we omit the parameter as we did here to allow the stack to choose the spacing.

When we allow the stack to choose the spacing, it does not choose a fixed value, but a value that makes sense for each specific pair of children.

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.

The width of the stack is the sum of the widths of its children, plus the spacing between them.

In the example above the stack has the height of the first child, the Image, which also happens to be the tallest. We can verify whether the height of the stack is that of the first child, or the tallest, by switching the order:

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

            Image("barbarian")
        }
    }
}

If the stack took the height from the first child, the Image would have had to overflowed the stack’s bounds; as we see, it does not:

hstack with the children in reverse order

This confirms that the height of the stack is the height of the tallest child. When we look at alignments we’ll learn that the height of the stack also includes the vertical positions of its children, thus a more general rule is that the stack’s bounds are the union of the bounds of its children.

the stack’s bounds are the union of the bounds of its children

We can confirm that the stack’s bounds are derived from the children by looking at what happens if the size of the device is larger than the combined sizes of the stack’s children:

hstack in larger frame

The stack does not expand to fill the size proposed by the device, its parent. The children of the stack chose their own sizes, and stacks are no different. Stacks choose their own sizes, derived from the sizes of their children.

Stacks when Constrained in Size

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

Let’s take a look at what happens when there is not enough space available for all of the children of the stack to be laid out horizontally.

We can do this by adding a .frame to the stack, which as we should recall, creates a new view with the stack as a child, and proposes the size given to it to the stack:

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

            Text("Nogistune Takeshi")
                .font(.title)
        }
        .frame(width: 280, height: 100)
    }
}

Since there is not enough space for both the Image and the Text to be laid out horizontally, the Text can help out by wrapping its contents, reducing its width by increasing its height:

hstack with wrapped text

But what if we had more than one Text in our stack, which one would adjust its size?

To see what happens, let’s divide our single Text into two, one each for the character’s family name and given name respectively. Since we want the Text to be truncated rather than wrapped, we’ll also throw in a .lineLimit:

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

            Text("Nogistune")
                .font(.title)
            Text("Takeshi")
                .font(.title)       
        }
        .lineLimit(1)
        .frame(width: 280, height: 100)
    }
}

Rather than allowing the first Text to take up most of the space, and the second to extra-truncated to fit, the stack instead divides the space up fairly between them and causes both to truncate:

hstack with two truncated txt

Yet while the space has been fairly divided between the Text children, the Image doesn’t seem to have been affected.

Proposed Sizes in Stacks

We gave a warning in views choose their own sizes to not confuse the proposed size a view receives from its parent with the size the parent chooses for its own, and now we can review why.

Like every view the stack receives a proposed size from its parent; in our examples, a frame or the device. And as we’ve already seen, the size of the stack is derived from the size of its children.

When the stack lays out its children, they receive a proposed size from the stack, but that proposed size cannot be the size of the stack since it has not been chosen yet. Neither is it the proposed size the frame received from its parent, which would not fairly distribute space as we’ve seen the stack does.

Instead the stack makes proposed size offers to its children in order of their increasing horizontal flexibility.

The stack begins with the proposed size it received from its own parent, and since it knows how many children it has and how much space to place between them, it can subtract the sum of the necessary spacing to obtain the space available for all of its children, which it can then divide equally

As the least flexible child, the Image receives the first offer of a proposed size. Since the Image is inflexible, it chooses a size without considering this. The stack subtracts the chosen size from the available space, and then divides the remainder equally again.

As the more flexible children, the two Text views next receive an offer of a proposed size, taking their equal share of the remainder and truncating equally.

In flexible frames we’ll learn how we can adjust a view’s flexibility by specifying minimum and maximum lengths.

Layout Priorities

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, stacks give us a way to control the order in which children are laid out.

By increasing the layout priority of certain children, the stack introduces an extra layout pass for those children before their siblings:

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

            Text("Nogistune")
                .font(.title)
                .layoutPriority(1)
            Text("Takeshi")
                .font(.title)       
        }
        .lineLimit(1)
        .frame(width: 280, height: 100)
    }
}

After subtracting spacing from the proposed size, the stack will consider children in order of decreasing layout priority. For those of the same layout priority, the stack as before will make offers in order of their increasing horizontal flexibility.

So in this example we’ve increased the priority of the Text containing the family name, ensuring that is laid out first, and gets the space it needs.

Next the two remaining children with a default zero layout priority are considered. Since the Image is the least flexible, that receives the first offer and chooses its size. Finally the Text containing the given name is proposed the remainder.

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, it lays out its children sequentially from its top edge to its bottom edge:

vstack

Just like the horizontal stack, each child view chooses its own size, and. the vertical stack itself chooses its own size, derived from the sizes of its children.

Vertical stacks obey all of the same rules and constraints as horizontal stacks, just in a perpendicular axis.

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, with the default alignment to be centered.

Vertical stacks can be particularly useful to demonstrate the default spacing behavior, for example if we split the surname and family name as we did for a horizontal stack example above:

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

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

The result includes spacing between the Image and first Text child, but no spacing between the two adjacent Text children:

vstack with no spacing between text

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 chooses its own size, and the z-axis stack itself chooses its own size too, derived from the sizes of its children.

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.

In this simple example all of the children are laid over top of each other. We’ll learn when we look at alignments that we can move them apart from each other, and in exploding stacks how we can combine alignments and flexible frames inside a z-axis stack.

As with the horizontal and vertical stacks, the bounds of a z-axis stack are the union of the bounds of its children; but with an added twist: only the bounds of its children with the highest layout priority. We’ll look at this in more detail when we look at secondary views.

Combining Stacks

More complex layouts can be created by combining stacks together, a topic complex enough to deserve its own post.


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

Views Choose Their Own Sizes

One of the first things presented about SwiftUI is that its views choose their own sizes, and that once chosen, those choices cannot be overridden. This is such a simple statement that it would be easy to move on quickly to get to the good stuff.

This would be a mistake, because this simple statement fundamentally changes everything you know about layout. Failing to take the time to learn SwiftUI will leave you constantly feeling like your fighting to achieve even the simplest layouts. This would be a mistake as the design of SwiftUI has significant benefits over UIKit and AppKit, and it’s worth learning to think differently.

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 used has a size of 101 × 92 pixels and the resulting view has the same size, centered on the screen of our device:

an image

The preview above is rendered using SwiftUI and includes additional borders to help visualize the layout. In this preview, and in future previews, a red border is used to show the bounds of images, and a gray border shows the bounds of the device. We’ll learn more about secondary views such as borders in other posts, but when they are only used to clarify the bounds of views in examples, we’ll omit them for clarity.

We can use the .frame modifier on the image to place it inside a frame:

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

Coming from UIKit or AppKit, this may not be what we expected:

an image inside a frame

Rather than stretching to fill the frame, the image has retained its chosen size and been centered within the frame’s bounds. In the preview above, and in future previews, we’ll use a green border to denote the bounds of frames.

It turns out that .frame does not modify the properties of the view it is applied to; it creates a new “frame” view with the specified size, and positions the view it’s applied to (the Image in this case) as a child inside it.

.frame does not modify the properties of the view it is applied to; it creates a new “frame” view with the specified size, and positions the view it’s applied to as a child inside it.

This is worth taking a moment to consider, because it’s a fundamental of SwiftUI. Modifiers like .frame create new parent views containing the view they are applied to as a child, and are extremely useful for building layouts of smaller views.

The size chosen by the Image view is based on the dimensions of the image and its properties in the asset catalog. This size cannot be overridden by its parent.

For an extreme demonstration of this, we can try and place the image 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 have expected the Image to be resized, at least in this case even if not the other, but in fact it simply overflows the frame’s bounds:

an image overflowing a frame

We can use this behavior to create overlaid views, or we can customize it through clipping modifiers such as .clipped or .cornerRadius to constrain the drawing to the frame’s bounds. Since this post is about layout, we’ll cover those in future posts.

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

In this preview, and in future previews, we’ve added a yellow border to show the bounds of text, and again used the gray border to denote the size of the device.

Just like Image before, the Text has been centered within the bounds of the device. Text too has a chosen its size, which is simply the size necessary to render the string given to it.

As with images, we can add a .frame to text views to build larger layouts:

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 just as it was previously centered within the device:

a text inside a frame

Text gives us a few different ways to influence the size it chooses, for example 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)
    }
}

As we would expect, since it’s now using a larger font, the view now has a larger size than it had before:

a text with a title font

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)
    }
}

We saw with similar code using Image that it kept its chosen size and overflowed the bounds of the .frame. But Text has already let us change its size through the .font modifier, so we might expect different behavior:

a multi-line text in a frame

Instead of overflowing the bounds of its parent frame, the Text was able to choose a different size that fit within it. In this case it wrapped its content, choosing a size lesser in width but greater in height.

This doesn’t contradict what we’ve just learned about chosen sizes not being able to be overriden, it actually demonstrates another key principle we haven’t learned yet: a view receives a proposed size from its parent before it chooses its own size.

a view receives a proposed size from its parent before it chooses its own size

It’s important not to confuse this proposed size a view receives from its parent with the size the parent chooses for itself. In most cases in SwiftUI a parent view chooses its size based on the size of its children. We’ll learn more about this later when we look at flexible frames.

Proposed Sizes

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 layouts.

The Text view chooses its own size, and once chosen that size cannot be overridden. But Text is also flexible as to what sizes it can choose, depending on the proposed size it receives from its parent; in this case the frame we added to it.

We’ve specified a size of 200 × 200 for the frame, so when the frame is ready to position the Text inside it, it proposes the same size to its child, and only then asks the child to choose a size. In the example without the frame, the Text received a proposed size of the bounds of the device itself.

In the example with the frame, the width proposed was insufficient to render the entire string, but there was sufficient height available to wrap the contents over two lines instead.

Once Text has determined the best way to layout its contents within the proposed size, it then chooses the size necessary to render its contents in that way.

The size chosen is still usually smaller than the proposed size, and always tightly hugs the content; it does not grow to fit. As you can see in the example above, it did not simply choose the proposed width as its own width, it chose the largest width necessary for the line-wrapped string, and it was still necessary for the frame to center the text within it.

Just as with Image being too large for the frame, a similar case occurs when a frame is added to a Text without enough height, for example:

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

Just as Image overflowed the proposed size from the frame when it was too small, so does Text:

text in a too-small frame

Text Truncation

Wrapping is not Text‘s only trick for choosing a different size, it can also tighten by compressing space between characters, and it can decrease its width and height by truncating its content, rendering a shortened version of the string with an ellipsis:

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

Here we’ve added a .lineLimit modifier removing the default unlimited number of lines and replacing it with a new value of 1, this forces Text to use a different approach for choosing its size when the proposed size is not sufficient:

Since the Text wasn’t able to fit the full width of the string in the space proposed, and was not permitted to use multiple lines, the Text instead decreased its width by truncating its contents.

Note again that the width chosen by the Text is only enough to render the truncated line, and it is still centered within the frame.

The same truncation can occur in the situation where there isn’t sufficient proposed height for the text to expand to multiple lines, for example:

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

Even though no line limit is specified, the proposed height from the frame is only sufficient for a single line, so the content is truncated to fit the proposed space in this example too:

text truncated by limited height

Resizable Images

Now that we’ve learned that a view choosing its own size doesn’t mean the view can’t be flexible about what that chosen size is, we can revisit Image.

By default images have the 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 chooses the same size proposed by its parent, entirely filling any available space:

a resizable image

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

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

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

a tiled resizable image

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 called .aspectRatio.

But we can’t just specify that alone, we still need the .resizable modifier since we want to constrain the aspect ratio of the image after making it resizable.

Modifier Ordering

When specifying multiple modifiers, the ordering matters. Since each modifier applies to the view it’s added to, adding a corresponding superview to the view hierarchy, this means that in SwiftUI modifiers are written from the inside out.

in SwiftUI modifiers are written from the inside out

To make a resizable image constrained by the aspect ratio of the source, we need to specify both modifiers, as well as the frame:

struct ContentView : View {
    var body: some View {
        Image("barbarian")
            .resizable()
            .aspectRatio(contentMode: .fit)
            .frame(width: 200, height: 150)
    }
}
view hierarchy of an aspect ratio constrained resizable image with a frame

In this example code, the .resizable modifier is added to the Image view, which means that Image becomes a child of the .resizable modifier just as it was of .frame in our earlier examples.

Then when we wish to add a second .aspectRatio modifier, it is the resizable image that we wish to constrain the aspect ratio of, so we add this modifier to the end of that chain.

Finally it is the aspect-ratio constrained resizable image that we wish to place inside the .frame, so the frame comes last. It’s important to remember that while the .frame is listed last in the chain, it is the top-most parent, and that the Image while listed first, is the bottom-most child.

The results are what we intended:

a resizable image with an aspect ratio in a frame

Based on the size of its source, and of the proposed size from the parent, the Image has chosen to use the proposed height of the parent, scaling the source up to fit, and has chosen a width that retains the aspect ratio of the original source.

Since the Image therefore no longer utilizes the full size of its parent, 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.