2026-02-10 • 23:12
Explore the core concepts of layout in SwiftUI. Find out how to build flexible, dynamic views that work seamlessly with the system, and discover essential debugging techniques to streamline your workflow.
This session was originally presented as part of the Meet with Apple activity "SwiftUI foundations: Build great apps with SwiftUI”. Watch the full video for more insights and related sessions.
Speaker: Cat Thomas
Part of: SwiftUI foundations: Build great apps with SwiftUI
Downloads from Apple
Transcript
Good morning. Hi everyone. My name is Kat and I’m a development tools engineer here at Apple. Over the past few years, I’ve worked on projects like the vitals app for Apple Watch, as well as the sleep experience in the health app on iPhone and iPad. Yeah, these are experiences that need to feel intuitive and look beautiful. Whether you’re glancing at your wrist or looking at your phone. Through this work, I’ve learned that strong UI layout fundamentals are the foundation that make this possible.
Today, I’ll cover the principles that are universal building blocks in SwiftUI layout. No matter which platform you’re working on. First, I’ll revisit some conceptual components of SwiftUI that are especially important for layout, followed by SwiftUI layout mechanics. Then I’ll take you through examples of how to get predictable results using SwiftUI.
So I’ll start with the fundamentals views. As Leah described this morning, SwiftUI view is a lightweight template describing what will appear on screen. A view is a template that represents the position, size, and hierarchy. SwiftUI creates lightweight view structs to describe what’s on the screen and discards them, making a new struct on following updates.
SwiftUI update engine only updates changed UI parts, ensuring predictable and efficient results based on state changes. This efficiency comes from Swiftui’s local update model. changes in one part of the view hierarchy don’t cascade to unrelated subtrees. So only the affected subviews are Rerendered. And SwiftUI. The view template controls everything from positioning and sizing to rendering and state updates. Now that I’ve gone over what views are and how Swiftui’s update engine works, I’ll show the mechanics of how these concepts come together when SwiftUI performs layout.
As I go over layout mechanics, I’ll be focusing on the trip card I built in the wishlist app. The trip card provides a great example of how SwiftUI handles dynamic layouts based on different states. In SwiftUI, layout is a function of the state. When I say state here, it doesn’t mean the at state property wrapper. I mean state in general, like the name of a trip such as Cali coastal trails here, or the URL of the trip’s image. SwiftUI inherently abstracts state from layout.
This is possible with other UI frameworks as well, but it requires some effort and discipline. But this abstraction is an integral part of SwiftUI. State drives the layout by dynamically controlling the UI. State also populates what’s inside each of those views, like the strings and images that are being displayed on screen. In other frameworks, state changes require manual updates to relay out specific parts of the UI. In SwiftUI, state, changes automatically regenerate the affected view templates, always ensuring that they stay up to date.
Now I’ll discuss how I built the layout for the trip cards in the wishlist app, as shown here. To explain how to abstract layout from state. The trip card is shown against a plain dark background. It has a vstack containing an image representing the trip and a text view with the trip name.
The trip image view has an overlay that displays the number of activities, but only if the count of activities is greater than zero. Otherwise, the text is never created and SwiftUI won’t render the activities overlay. The state of the view is controlled independently by a trip from the model.
The trip model has a name string for the title of the trip. and a photo URL to determine which image to render for the trip. And lastly, it has a dictionary of activities. The count of activities in the dictionary is used to determine if the activities overlay is displayed. This is a simple example of state controlling the SwiftUI layout and how the value. In this example, the number of activities is used to customize the layout of the views.
Next, I’ll explain how layout works in SwiftUI. The basic premise of SwiftUI layout is that a container proposes a size to its subviews. The size of this proposal is constrained by the container view and all its previous container views. Each Subview responds by calculating and reporting back its preferred size. Different views in SwiftUI have different preferred sizes. A color view always claims whatever it’s offered. It grows to fill the available space.
Image view always claims its full size, even if it’s offered less space. This means images can spill outside of their containing views. Use the resizable modifier to override this. Then it will claim whatever space is offered. Just like color, even if it ends up appearing squished like with the coastal flowers here. To preserve the image’s original aspect ratio. Set the content mode to fit or fill.
Text claims at least enough space to show an ellipsis. Text will claim only the space it needs for its content. Layout and SwiftUI happens hierarchically. A container view proposes a size to each of its subviews. Suppose the proposed size is specific. For example, this orange box shows what a proposal could look like.
If the container view is the root view of an app. The proposal is the device size or. If you apply a frame modifier, the proposal is the size of the frame. On the other hand, sometimes the proposed size is unspecified in one or both directions. This means that the container view wants to know how much space the Subview would want if it wasn’t constrained at all. This is the subviews ideal size.
Each Subview responds with its preferred size given the proposed size. This is a special and unique behavior of SwiftUI compared to other view systems. Proposals don’t just go down, they also go up. This layout process is recursive. Proposals flow from the root view down the entire view hierarchy. Then responses flow back up.
With the sizes in hand. The root view tells all its subviews where they should draw. They tell their subviews, and so on until all the views are drawn. Now I’ll take you through the same traversal again. Again, this time with some actual views and actual values in the trip card here.
The trip card appears in a horizontal stack inside a horizontal scroll view. I want all the trip cards Kali, Kyoto, and others to have the same height, regardless of the size of the source image or the length of the trip name. I can achieve that using fixed height frame.
SwiftUI offers unlimited width and the fixed height of 220 to the Vstack. At the root of the trip card. The stack then offers the size to the trip imageview the height of 220. Here is just an example of a fixed height frame. The Imageview responds with the exact size it needs 185 by 150 points.
The outer stack subtracts the returned height, then offers the remaining space to the text that contains the trip name. The name text replies with the space it needs 15 by 130 points. I’ve reached the last view. Now each view has replied with the size it needs. That is each view besides the activities overlay. I’ll get back to that one later in the presentation.
Completing the process. The Vstack combines the requested size of the inner image, view and text, and returns the final requested size of the view. So that’s how SwiftUI layout works. To recap, SwiftUI layouts are traversed from top to bottom. And size information is shared back up to the root. At any level, the space available is provided by the container view.
The space needed is determined by the Subview, possibly by checking with its own Subviews. The layout is recomputed only when state changes. The view template is always recreated. State controls the content, so when the trip value changes, the view changes automatically. Awesome. It’s time to create some stunning SwiftUI views.
When I was just starting out with SwiftUI, I would build a view and sometimes not get the results I expected. So next I’ll share some guidelines to get predictable results by keeping your layouts simple and composable. The first consideration for predictable layouts is layout priority. Layout priority is used to determine the order in which views are laid out for spacing and resizing.
When there is not enough space to meet the request of the view, more space is given to views with higher layout priority layout priority is set with the layout priority modifier and the default value is zero. The higher the value, the higher the priority. So for example, a priority three view will be higher priority than a priority one view. This is probably the opposite of what you’re used to in your bug queues, where p1’s are the highest priority, but in this case it’s the opposite. The higher the number, the higher the priority.
In the wish list app, I created a section of trips where the cards have a wider width. I was chatting with our wishlist app designer Maho, and we think these wide trip cards are the perfect place to take advantage of the extra space and display the trips subtitle and creation date.
Sometimes with multiple text views in a container view. The text might be truncated or wrapped based on the available space. In this example, once I added the subtitle and date the Hawaii United States text on the bottom left hand corner of the card became truncated. I might not know how much space is available. For example, it can vary with different sized devices. and I might not know how much space I need. Some trips have longer subtitle strings, so annotating the importance of various text in the layout gives you the power to decide which text to truncate.
You can do that by increasing the layout priority of the text from the default value of zero to a higher value like one. As I shared earlier, the stack offers space to the higher priority views first, so the subtitle gets all the space it needs, but this forces the date text in the bottom right hand corner to truncate.
I shared this layout problem with our designer, Maho. She suggested hiding the creation date when there is not enough space, so the trip subtitle is fully visible. I think that’s great advice as it avoids string truncation. Now I’ll show how to do that. I moved my stack inside a view that fits. View view that fits. Evaluates its Subviews. In the order you provide them to the initializer, it selects the first Subview whose ideal size fits within the proposed size.
This means that you provide views in the order of preference. Usually this order is largest to smallest. So I added a text view with just the subtitle. When the view’s in, the stack would truncate view that fits will choose the text view. I’m sure these changes will make our designer Maho happy.
Next, our stack alignments stacks place their views to have matching alignment. The default alignment is center, but other alignments can be specified. In addition to center, the views in an H stack can also be vertically aligned at their tops or bottoms, and also on first text baseline or last text baseline. The views in a V stack can also be horizontally aligned at their leading edge, trailing edge, and others.
The section header for trip collections in the wishlist app is a perfect example. By default, stacks use center alignment to align views, but sometimes when you have a series of elements, this can appear misaligned. In this case, it’s best to have them all baseline aligned. Here the subviews of the section subtitle are using the default alignment. That means the show All is floating above the visual line established by the subtitle text. By changing the alignment to first text baseline, I bring the show all up to the same visual line established by the subtitle.
Now I’ll share a few more things to consider while building layouts in SwiftUI. Wherever possible, create adaptive layouts instead of explicit layouts. Adaptive layouts let you more easily move to different device sizes, window sizes, and platforms. Avoid manipulating the view frames explicitly instead of using explicit heights and widths for views. Let them expand to fill the available space.
Only use view modifiers such as frame or position, when the desired layout can’t be implemented in an adaptive, flexible way. Similar to Zstack. To add depth to your view, you can use background or overlay modifiers. Overlay and background modifiers are not included in the layout sizing and always will have the same size as the view they modify. This is why in the layout tree traversal example, earlier the activities overlay was not included in the layout calculations. It inherited the calculated size of its container view. The image view by default.
Layout issues happen. I still sometimes encounter them. Now I’ll share some techniques that I use for identifying and fixing those layout issues. Many of these are based on the power of SwiftUI for rapid prototyping. I can make a small tweak and the results will appear immediately. One simple technique is to add colored borders like red on the image and blue on the text, as I’ve done in the trip card here. This technique is especially useful for understanding stack layouts and padding around views.
Sometimes temporary borders overlap, and I want a stronger representation of the size and position of subviews. In this case, I use overlay modifiers with translucent colors to reveal the full extent of subviews and how they layer. It can also be useful to print the value of a property when a view is rendered. You might have tried using print And been disappointed by a compiler error. Here the error reads build expression is unavailable. This expression does not conform to view.
This error is because the print expression in Swift has no return value, so its return type is void. And void isn’t a view, but the body of a view does support variable declarations. I can fix this error by declaring a placeholder variable. Now I can write my print expression on the right hand side of the equals sign. This compiles without error and prints output to the console when running.
Next up, Xcode previews the preview canvas, and Xcode is a quick way for seeing what view goes with what code. Click the mouse pointer icon at the bottom of the canvas to make the preview selectable. Then, when I select code in the editor, the preview highlights the related view, and if I select a view in the preview, Xcode selects the corresponding code in the editor.
The most powerful tool in my toolbox for debugging layout issues is Xcode’s View Debugger. By running my app and stopping it with the view debugger, I can explode all the views, then select individual Subviews to focus on. This technique is particularly effective when I’m mixing SwiftUI with UIKit. Which brings me to an important point. SwiftUI is compatible with UIKit layouts. As James Graham from Alltrails will share later. To learn more about it, check out the Wwdc 22 video. Use SwiftUI with UIKit.
Now that I’ve shared the fundamentals of layout in SwiftUI, it’s time to put these concepts into practice. The best way to learn is to start building. I got started by picking a view from an app I liked and remaking it in SwiftUI. Xcode previews is a great way to quickly try out different layouts and approaches. Use simple techniques like print and color overlays to diagnose any layout issues you encounter. You’ll be amazed at how quickly you can identify and fix the problem. Thanks for joining me today. Now go build something special with SwiftUI and enjoy the rest of the sessions.