2026-02-10 • iOS, iPadOS, macOS, tvOS, visionOS
Learn to build great apps with SwiftUI in this special all-day activity streaming from the Apple Developer Center Cupertino. Whether you’re just starting out or have experience creating with SwiftUI, this series of foundational sessions will help you sharpen your understanding of core concepts and write strong, performant code. Learn from James Graham, CTO of AllTrails, about how his company took advantage of SwiftUI's capabilities. And dive deeper in a Q&A with members of the SwiftUI engineering team.
Speakers: Leah Womelsdorf, Majo Corino, Cat Thomas, Curt Clifton, Cole Imhoff, James Graham, Nick Teissler, Russell Laddni, Taylor Kelly
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Intro
Hello. Yay. I know. Hi, and welcome to the Apple Developer Center in Cupertino. My name's Leah Womelsdorf, and I'm a technology evangelist. Today, we're excited to share the foundations of SwiftUI. And there's a lot to cover. But first, I want to share more about our venue. The Developer Center is part of Apple Park, and it's a great venue for today's event. It has different areas for connection and collaboration.
This room is Big Sur and it's a beautiful space with a great setup. It's designed to support a range of activities including in-person presentations, studio recordings, and live broadcasts. There are also labs, briefing rooms, and conference rooms that allow us to host activities like this and many more.
This is one of four developer centers around the world where we host designers and developers for sessions, labs, and workshops. Has anyone here already been to a developer center? Nice. Welcome back. Whether you're new or returning, we're so happy to welcome you today. We also have many people joining us online right now. Hello, thanks for tuning in.
Our Developer Relations team loves connecting with developers to make the best apps for Apple platforms. Since WWDC, we've hosted over 300 activities, including labs, workshops, and presentations. Later this week, here in Cupertino, my team's leading a workshop on the new design. Developers will get hands-on experience with Liquid Glass as they update their code and designs. To learn more about upcoming events near you, check out developer.apple.com/events.
Before I get started with today's agenda, I want to share some tips for people in the room. To stay connected throughout the day, use the Apple Wi-Fi network. And if you need to recharge at any point, there's power available at every seat. It's in front of the armrests.
For everyone tuning in today, these presentations are meant to be a special experience just for you, both online and in person. So please refrain from recording video or live streaming during the presentations. On the other hand, photos are totally welcome, so take as many as you like. After the event, we'll send out some follow-up information with resources to check out so you won't miss a thing. With that small request out of the way, I'm excited to share more about today's event.
SwiftUI is Apple's declarative user interface framework. Like any tool, SwiftUI has foundational concepts. Taking the time to learn these concepts will empower you to use it well and build something incredible. With SwiftUI, the end result isn't just an app. It's an app that can thrive on all Apple hardware. It's intuitive and familiar, even for people using it the first time.
SwiftUI apps are able to stay modern with the latest updates to the operating system, like Liquid Glass. SwiftUI is used widely within Apple, both as the basis for new apps and the evolution of existing apps that started out with UIKit. It was built with incremental adoption top of mind, so you can use it in your code base gradually.
Now more than ever, it's important to take the time to learn the foundations of SwiftUI. There are so many ways to write code, whether line by line or using LLMs and agents. Understanding the foundations will enable you to understand and have more confidence in your code. Our team has been working on a new app, Wishlist, that's made 100% with SwiftUI. Wishlist is the basis for all of today's presentations to demonstrate how the foundations of SwiftUI come together to make a real app.
my teammates and I love to travel and I have so much fun planning my next vacation. I like to plan them out in depth so I can squeeze the most out of a trip. Not just the places I'll go, but the things that I'll do there. Thank you. Wishlist is a travel bucket list app. We built this app to track the places we want to go, the activities we want to do, and reflect on our progress as we hold ourselves accountable to dream big and try new things.
There are two main types of data in the app, trips and activities. Trips are the locations I want to travel to, like Kona in Hawaii. and activities are the things I want to do on my trips, like go snorkeling and whale watching. There are three main things to do in the app. By tapping the plus button in the toolbar, I can plan a new trip by adding a title, a photo, and the time of year.
While I'm on a trip, I can check activities off of my list, like snorkeling with the Manna race. I can also reflect on my progress. For instance, this fall, I took a trip to Montana and I earned the Fall Explorer badge. To navigate through these experiences, Wishlist uses a tab view.
We chose a tab view for consistent navigation. My colleague Maho will explain some of these decisions in her design presentation. There are three tabs in Wishlist. In the Wishlist tab, I can scroll down to browse all of the trips I have planned or add new ones. When I tap on a card, like Kona Deep Dive, I'm taken to the trip details page where I can check things off.
The Goals tab is where I check my progress towards awards. In the top row, I see the awards I've recently won, like Fall Explorer. When I scroll down, I can see my progress toward my next milestones. I've already checked off seven activities, so I'm just three away from earning the 10 Activities Award.
The third tab is the search tab. I use search to quickly find what I'm looking for. Once I've already added a bunch of trips, it's really helpful to search and dive into the exact one that I want. My colleagues will tell you more about how we built the app, but perhaps more importantly, the foundational concepts to make each decision. These foundations are the most important thing to take with you today. Apply this theory to your own code so that you can reason about it with confidence.
Wishlist was a lot of fun to make at every step from ideation to design and actually building it out. And this app sets the scene for the rest of today's presentations. questions. In the coming weeks, we'll have the sample code for Wishlist available to download from our website. And I'm so excited about this. It's a great way to follow along and cement your learning. Along the way, if you have questions about the examples or SwiftUI in general, you can use Slido to ask questions to Apple engineers.
Our team is eager and ready to support you. And you can ask questions about the examples or any SwiftUI question. You can also upvote questions from other developers and read the answers. It's a great resource for learning, so I encourage you to check it out. All right, now it's time to dive into the addenda.
Today's presentations cover foundational topics of SwiftUI. And this is really important. Foundations are useful whether you're just starting out or you already have experience and want to sharpen your understanding of the framework. Even if you're already familiar with SwiftUI, developing a deeper understanding of the foundations will help you choose better implementation strategies and solve problems in your apps.
I'll kick things off with an overview of SwiftUI. After, Maho will share how SwiftUI empowers you to design with the system. Then we'll take a quick break and reconvene at 1115 Pacific. Then Kat will guide you through the layout system in SwiftUI. Kurt will follow that up with SwiftUI in motion, covering techniques for animation and visual effects.
Then we'll take a break for lunch to grab some refreshments and reset. We'll meet back here at 1:30 Pacific, when Cole will demonstrate how to model your intention, model your data with intention in SwiftUI. And then we have a special guest. James Graham, the CTO of AllTrails, will share key learnings about their journey incrementally adopting SwiftUI from a complex, mature UIKit app.
We'll take one more break to stretch and grab some coffee, and then end the day with a panel of leaders in SwiftUI engineering. They'll answer some of the most popular questions from today and share their vision for the framework. for people here in Cupertino. After that, we'll have a mixer with some light refreshments and an opportunity to connect with Apple engineers and designers. All right, now I'll kick things off with my presentation on SwiftUI Essentials.
SwiftUI essentials
I'm Leah, and I'm a technology evangelist. Before I joined the evangelism team, I was an engineer here at Apple. I worked on some of my favorite apps, the fitness app, the workout app, and the health app. And I even had a brief cameo in the 2021 keynote. This is pretty cool, right? It was super fun to film. I got to run around Apple Park.
I started in Apple in 2019, the same year that SwiftUI was announced. Over the years, I've learned a lot about the framework, how to use it, but more importantly, how to use it well. When I first started using SwiftUI, I loved how quickly I could prototype and build real functional features in an app. With a few lines of code, I could make something real, and that was awesome.
This is something that many software engineers go through, regardless of the tool or the framework. It's super exciting to get something on screen, but sometimes it doesn't always work like you expect. So I needed to take a step back and start with the basics. I needed to understand how SwiftUI works so I could make better quality software. Thank you. Today, I'll share concepts that are central to how SwiftUI works.
These concepts will help you write better code, and they'll also set the scene for the rest of today's presentations. I'll start by taking a closer look at a term used a lot in SwiftUI, view. Then I'll cover the views that are built into SwiftUI and the logic behind making new custom views.
Finally, I'll cover dependencies and how to structure your code with intention. In each section, I'll share the most important concepts for each and how they relate to the bigger picture of writing great SwiftUI code. All right. In SwiftUI, the term view is an overloaded term. Depending on the context, it can mean three different things, and this can be a little confusing.
In many frameworks, view refers to the user interface and pixels on the screen. In SwiftUI, a view is also a protocol. It's a description for what you want on the screen. Because each view is a struct, each instance of a view has a particular value. These three concepts are all related. The pixels on the screen are a result of the description of the view and the actual values of that particular view struct. the description of the view, and the values that drive it result in the final pixels on the screen.
These three meanings of view come up in different areas of an app. The description is the code itself. The value is the instance in memory, so SwiftUI can render the pixels. and the pixels on the screen need to show the latest, most accurate information. I'll go into each of these different meanings of view throughout my presentation. The context is important and it will help you think through problems in your own SwiftUI code. I'll start off with views as descriptions.
In SwiftUI, views are descriptions with intentional ways to build them. Let's say I want an image of a cool underwater scene with snorkeling. In English, to describe this, I'd write the phrase "image of snorkeling underwater." In SwiftUI, I build this description with the view protocol. Image is a SwiftUI view, and Deep Sea is the name of an image.
Just like my phrase in English, views describe what I want in SwiftUI. SwiftUI takes the view and decides what to render on the screen, like this nice photo. All SwiftUI views are descriptions that are written with code. This is true whether they're built-in views or custom views that you write yourself. Image is an example of a built-in SwiftUI view.
Views are the building blocks of every app, and the built-in views are a great way to get started. There are many built-in views in SwiftUI, and each one's name describes what it produces. For instance, similar to how image visualizes an image, Color is a view that fills the entire frame with a particular color, like purple.
Built-in views are powerful because SwiftUI integrates them with the hardware. Here, this purple is actually a context-dependent purple color. the actual color values adjust based on the context of the device, like if the phone is in light or dark mode, or whether the sun is shining bright on the screen.
Text visualizes strings, like Kona Deep Dive. Like color, text views are optimized for the context of where they exist. SwiftUI draws the string using a font that's appropriate for the current platform. For larger displays like an iMac, the text view is going to be physically larger than smaller ones like on an Apple Watch.
TextViews also support dynamic type, which is an accessibility feature. Dynamic type lets people adjust the size of visible text on their devices so they can comfortably read it. Here, the phone on the right has a larger system font size. The text views in my Swift UI code automatically scale so that the content is still readable. And this doesn't require any additional code on my end.
For each view, image, color, and text, the view's name is a description of what I want. Combined with the values of Deep Sea, Purple, and Kona Deep Dive, they result in pixels on the screen. built-in views are a great starting point because they're not just useful as is, they're also customizable.
Customizing views lets you express the unique personality of your app while leveraging the benefits of SwiftUI's built-in views. There are several ways to customize views in SwiftUI. My text view from before looks all right, but I can make it more memorable. View modifiers are a tool to customize individual views. I'll dive into some code.
there are many ways to customize TextViews. For instance, changing the size or the color of the font. That's something we did a lot in the Wishlist app. For now, I'll start with something simple. I want my text view to visually pop against a bright color. The background modifier sets the background of my view. You can provide any view as a background. I chose the color orange. Background is an example of a view modifier. A view modifier is a method that's called on a particular view, like text, and it returns a new view that contains the original.
The original text view is wrapped in changes from the view modifier, and it produces a new view. I like to think of view modifiers as wrappers that surround views. Together, these two lines of code produce the original string, Kona Deep Dive, with an orange background. It's a slightly modified version of the original text view. you can apply multiple view modifiers to create a compounded custom effect.
Padding is another view modifier. It adds points to the edges of the view that it's applied to. In this case, padding adds points on all four sides of the text view. And then the orange background fills it in. Just like background, padding wraps everything before it. So all three of these lines of code represents one big view in the view hierarchy. Thank you.
One final note on view modifiers: the order is important. Each view modifier only affects the lines of code above it. If I swap the order so that background goes first, the color orange only covers the original text view. The padding, shown with the gray dotted box, is applied to the rest. Be intentional about the order of your view modifiers. If your code isn't behaving the way you expect, check to confirm that your view is wrapped in an order that makes sense.
While view modifiers customize individual views, composition is a technique to combine multiple views. You can build your own custom views by combining existing ones. Let's say I want to build one of these rows in the Search tab of Wishlist for Kona Deep Dive. I can use some of my built-in views from before.
First, I'll define my view, search row. It's a struct, and it conforms to the view protocol. Then I'll add the body of my view. This is something that's required for all views. And I'll add an image view of the deep sea and my text view that says Kona Deep Dive.
By default, SwiftUI stacks them vertically. I want them to be side by side, so I'll add a horizontal stack, or an HStack. Like image and text, HStack is a view. But instead of describing what to render, like an image, it describes how to render it. SwiftUI HStacks arrange all of the subviews, like image and text, in a horizontal line.
Later today, Kat will go deeper into the tools and techniques for layout in SwiftUI. I want to call something out. Thank you. Even as I make more sophisticated views, the core concept is the same. My search row is a description of what I want. And SwiftUI takes that description to render the pixels on the screen.
Composition makes code easier to read. And once I've built my search row, I can use it in other areas of my app with just a single line of code. In my search view, instead of needing three stacks, three images, and three text views, I just use three of the composite search row views.
And later on, if I want to change anything in the search row, like make the background purple, I can do it local to that view. This reduces the amount of code I need to write and makes it easier to read and reason about. Because views are structs, they're lightweight. So splitting up a view into smaller views like this doesn't hurt performance.
Just like built-in views, custom views are descriptions of what SwiftUI should draw on screen. Thank you. The names, like search view, are important. But even more so are the views inside of body and the order that they're listed in. SwiftUI uses the descriptions in your code to draw the pixels on the screen.
Now, I want to take this one step further. My search row is pretty close to the intended design, but it's not quite there. Right now, I have three instances of Kona Deep Dive. And I don't know about you, but I like to switch up my vacations every year. Apps are dynamic and represent data that changes. I need a way to switch this up so I can show the other trips on my bucket list.
This brings me to my last topic, dependencies. SwiftUI views are driven by data, whether that data stays the same, like my image of the deep sea and the string KonaDeepDive, or that data changes. All of the views I've shown today are dependent on data. The pixels on the screen are partially a result of the description, like image, and partially of the data, like deep sea.
I mentioned earlier that all views are structs. And that means that all views are value types, and every instance of a view has a value. I like to visualize an instance of a view like this, with the name of the view on top, like image, and the data, like deep sea, below.
SwiftUI takes a particular instance of a view, including the data, to render the pixels on the screen. Once the pixels are rendered, SwiftUI discards the instance. It doesn't need it anymore. This is the third meaning of view. A view as a value. Instances of a view, the particular values, power the pixels on the screen.
Each view instance exists transiently. They're short-lived. They're created when they're needed, living in SwiftUI's memory. And then once the pixels are rendered, the view's job is done, and the instance is discarded. Note that discarding them isn't a bad or a high stakes thing. Views are lightweight and easy to create.
I like to think of views as templates, able to stamp out the same thing, creating pixels on the screen many times. These templates represent data that can be flexible and dynamic. For instance, in my app, I've been focusing on the deep sea, but I also need another image of Kyoto.
This is another instance of a view, but this one has a slightly different value. The name of the image, Kyoto, is different from deep sea. SwiftUI takes these image views, which have different values because of the image names, and it produces different images. Now, I'll take this flexibility and revisit the code for my search row.
Now, instead of hardcoding the name of the image and the text, my search row takes arguments for the name of the image and the title of the trip. This version of my search row is more flexible. I can still use it to make the Kona deep dive row, but I can use it for other trips too. This gets me closer to the real design.
When I use the new search row in my search view, I provide all of the different images and trip names, like Mammoth Blush and Kyoto Mystique. And this is much better. The data is driving the pixels on the screen, and I'm still able to leverage the clean code from composition.
Keep in mind that this implementation of the search view is still a simplification of the final version in Wishlist. The actual search looks up the data dynamically and then populates the search rows based on the images and strings entered in the search field. I want to highlight one more thing about these search rows. Just like the image views from before, each instance of the search row has a different value based on the trip name and the photo name. SwiftUI can easily compare these and notice they're not equal. After SwiftUI draws the pixels on the screen, it releases the instances of the views. It doesn't need them anymore because the pixels are rendered. Now, I want to talk a little bit more about how SwiftUI works with dependencies.
For my search row, the description is my view code. Different values of the view result in different pixels on the screen. SwiftUI tracks important values with dependencies. And by important values, I mean the kind of values that can make the pixel seem right or wrong based on the data. Here's how it works.
The first time that SwiftUI runs body, it keeps track of all of the values it reads with a graph. The first part of this graph is a node for search row. In body, SwiftUI reads the value of photo name for the image view. So it adds a node for photo name and an arrow that points down to search row.
SwiftUI also reads the value of trip name. So it adds a node and an arrow for trip name too. SwiftUI tracks this because for the pixels to be correct, they need to represent the right photo name and the right trip name. If either of those change, for instance, if I provide a new photo name with I'm representing with this red dot, then the pixels for the search row would be out of date, and SwiftUI would need to draw new ones. Dependencies are like the inputs of a view.
Dependency tracking is one of the reasons that SwiftUI is so efficient at making updates. In an app with a lot of views and a lot of data, it would be extremely inefficient if every time one thing changed, SwiftUI needed to redraw everything. data changes frequently in apps, and this would cause a lot of unnecessary updates.
Instead, SwiftUI tracks dependencies. I'm representing it here with arrows, and they indicate which views depend on certain pieces of data. With this system, when one thing changes, SwiftUI only recomputes and redraws the things downstream. Dependency tracking keeps SwiftUI efficient as things change quickly and often in your apps. This is sophisticated and there's great news. You do not have to build this graph yourself.
SwiftUI builds it for you automatically in its dependency graph. It keeps track of the properties that each view reads every time it runs body. This system keeps the graph continuously up to date. even though you don't have to build or maintain this graph, there are still some things for you to keep in mind for your code.
Keep views light. When building your views, consider what information you've told SwiftUI that you need in body, and whether changes to those views should invalidate the entire view. It's best to keep your views light with minimal dependencies, so SwiftUI only redraws them when it's important. This doesn't mean that you can't have complex views. It means that you should break those complex views down into smaller, simpler pieces, like I did earlier with the rose in my search view.
Use the right tools to represent your data, and how to communicate changes only when they should change the pixels on the screen. There are a few different ways that a view can depend on data. One way is to pass that data directly into a view, like I did earlier by providing the specific names of images and trips. Another way to create a piece of data is with @State. My colleague Cole will go more into depth into @State and each of the other options for Dataflow in his presentation. But I want to cover some of the basics.
Properties wrapped with @state tell SwiftUI to store that piece of information for the entire lifetime of a view. That's the entire period of time that the view is in the view hierarchy. I'll demonstrate with a simple example. girl. In the Search tab of Wishlist, as I start to type in the search bar, the results filter down. When I add the letter J, the most recent items are replaced by my trips and activities that include the search string, like hike Joshua Tree and cliff jump.
When I add another letter, the letter O, the results continue to filter down. Out of all of the trips and activities that I've added to my app, Joshua Tree is the only item that includes the string "jo". The state of my app, the string that I've entered into the search field, drives the results and the pixels on the screen. I'll build a simplified example to demonstrate how @State works.
This is a simplified version of the search view, which I'm calling simple search. It has the core functionality of the real search view, but some simplifications in the design. Thank you. There are only two views in the body. I'm using a text field and a text view. The text view is just a placeholder that says results here. This is something I do a lot while I'm prototyping. I put placeholders in. I'll eventually replace it with a custom view that shows the search results.
The text field is another built-in SwiftUI view. It's meant for collecting input. When people tap on it, the keyboard opens, and it displays the string that I've entered. I provide a starting prompt, type here, and then tell SwiftUI to store the string that's entered in the variable named searchValue. The dollar sign is syntax for a binding, which is another data flow tool that Cole will go into later.
I decorated the property search value with the @state property wrapper. This tells SwiftUI to hold onto the value of search value across updates. The initial value is an empty string. When I tap the text field, the cursor blinks, indicating that I can type. As I type the letter j, the value of "surgeValue" is changed from the empty string to j. SwiftUI hold on to that value and can update the pixels for simple search accordingly. The same thing happens when I add the letter O to keep filtering down towards Joshua Tree. SwiftUI holds onto the value of search string, "jo", so it can make continuous progress across updates.
@State is the simplest way to model information that should stick around for the entire lifetime of your view. For the entire time that I'm in the search tab, my search value will persist so I can keep adding to it. If I navigate away from the tab and then I come back later, the value will start from scratch again with an empty string.
This is just the first step of building the real search view in Wishlist. Next, I'd need to replace my placeholder text view with the trips and activities that match the search value string. For now, focus on these aspects of state. @State is a property wrapper in SwiftUI. When a property is marked with @State, SwiftUI knows that it's a source of truth for my app, and it holds onto that value across updates.
state remains in memory for the entire lifetime of the view. This means that apps can display data that's dynamic and compounding, like the string typed in the text field. There are multiple ways to structure dependencies in SwiftUI, and Cole will go into each of them later. For now, I want to emphasize one thing. Invest the time to learn how these tools work. This way, you can ensure that you're modeling your dependencies to work with SwiftUI.
As I've covered today, views are the building blocks of SwiftUI. By understanding more about the different contexts they're used, you can understand not just the framework, but improve the quality of your code. For the rest of today's presentations, as you explore different areas of Wishlist, I encourage you to focus on the theory behind these solutions. This way, you can apply those learnings to your own apps. For my presentation, keep these things in mind.
Leverage SwiftUI's built-in views to create your own custom views. Explore different modifiers to express what's unique about your app. As you build those views, or you revisit existing ones, remember to keep the views lightweight. Consider what data each view should really depend on, and break complex views down into simpler, smaller pieces.
Finally, invest the time to learn about SwiftUI. By keeping these foundations in mind, you'll be better equipped to reason about challenges as they come up. Thank you so much for joining me. Now I'm excited to hand it off to my colleague Maho. She'll tell you more about how she designed Wishlist with the system. Please join me in welcoming Maho to the stage.
Design with SwiftUI
Hi, everyone. Thank you for coming. Oh, so sorry. In the control, can we put the presenter notes bigger? I cannot read. Thank you all for coming. With the entire team, we designed a wishlist just for you and this event. And we are very excited to share with you how that process came to life. So hopefully you can transfer a little bit of my learnings and my design process throughout the years into your own. So let's begin. I'll start with app navigation, which starts with creating concrete sections for your content and your functionalities. That's kind of how we like to start. We need to start planning with knowing what exactly we're building. So navigation is kind of like a droid topic, but I personally love it. So we're going to kick off with that. Next, I'll go into layout and talk about finding the best way to present content in screen. And finally, I'll dive into visual design. This is where components and visual elements come together to bring clarity and personality to your app.
So as I mentioned, I designed this app from scratch. And the very first thing I did was thinking about the app navigation. Deciding the content and the features that the app will have will give me a good structure. So you can build with clarity, and you'll know how to use navigation components, like the top bar and the toolbar.
I started by brainstorming and listing out everything my team and I wanted the app to have and do. We wanted to celebrate goals and all ideas were welcome. We were on this brainstorming stage. The designer said only for a minute. From there I simplified and I gave only the essentials for a travel bucket list app, like I create trips, add photos, search, and then I step back and group related ideas together, like trips and activities, and progress and celebrate completed trips.
These groups represent the top three sections of the app. So I named them wish list, goals, and search. Now, this is our app, this is what it means to you. Whether you're starting from scratch or revisiting an existing app, it is incredibly helpful to go through this exercise. I've done it with developers here in the Developer Center for a while in workshops, and it's always very eye-opening. So I encourage you to do the same. So it helps you organize your thoughts and turn all these loose ideas that you have in the brainstorming into connected screens that now you will be ready to build.
I'll explain how all of this connects to SwiftUI. I know that this is what you're here for. I'm getting there. In iOS, there are two components that support app navigation-- the top bar and the toolbar. First, I'll show you how the top bar works. The top bar shows the top level sections of the app, and it remains visible across all screens.
In this example, the app can feel more complex than what it actually is. We all see in these apps where, you know, the more features I have, the more things the tab keeps packing. But not a lot of people like that. It gets very complex. So one thing I always recommend is keeping the number of tabs low. It helps you stay simple and it's very predictable. It reduces decision making. So whenever I open the app, I know exactly what options are available to me.
Second, keep your tab bar native. Is this in your bingo card for today? So SwiftUI components, as you know, like the tab bar, come with built-in behaviors, like animations and accessibility support. So these kind of behaviors can easily get lost if we customize. But don't worry, there'll be plenty of other places in your app where you can express personality. The top bar is just not one of them.
Finally, make sure the top bar uses clear symbols and labels that match what is inside each tab. Wishlist doesn't have a universal symbol, right? So I chose a rainbow, which is still recognizable and captures trips people aspire to take, right? Like it's hopeful. The goals tab on the other hand uses a symbol that it's more explicit and matches its label very well and has the same shape as many of the collectibles inside, which I thought it was a nice touch. Your top bar can be just as effective as efficient if you follow some of these guidelines.
Now I'm going to take a quick dig through. If you want to dig a little bit more into design or top bars, Visit the human interface guidelines. You have heard that from us before. The Hague is the home for design guidance and best practices across all Apple platforms and technologies.
This is your first stop for questions and advice before you come to us. And you can finally find a link for the Hague in the design section of the developer website. which also contains the incredibly valuable Apple Design resources. The designs for the Wishlist app and almost every other app I've designed in my life use native components from this library.
And while you're browsing and building your design toolbox, why not download the SF Symbols app? It's Apple's library of iconography. It has more than 7,000 symbols that you can just copy and paste into your designs or code. These are the ones that I use for the top bar, and it won't be the last time I'll refer to it today.
So OK, back to the talk track. Both the top bar and the toolbar support app navigation. But there's a difference, right? They act at different levels. People will use the top bar to move across your app. We said top level navigation, right? But when it's time to take action and to go within a particular section, we'll use the toolbar.
The toolbar has different elements to support navigation. First, the title of the current view. In this screen, you can see that people They can tell that they are in the wish list section. And they have some context about the content of the screen. Kind of like sets the tone. And then the toolbar shows the control for the most important actions that are on screen.
Here, the primary action is to create a trip. So I place a control in the top right to do so. The third element of the toolbar is navigation controls. Here in the trip detail, I added the back button that allows people to pop back one level and reach wishlist again.
To keep it easy to scan, I minimize the number of actions, same guidance, right, as in the top bar, and make sure the meaning is clear by choosing familiar sub-symbols. When there are too many items, like clearly in this example, it's difficult for people to understand what is expected for them to do first. So to fix that, I can simply find the actions that are less common or are there more advanced and hide it behind the More menu.
Toolbars also offer a prominent style for key actions. It adds a pop of color and creates an instant focal point. When you're designing your app, make sure to use only this style for one action per screen at tops. I know that there are a lot of prominent features that we might want to highlight, but you know, if everything is important, nothing is important.
So in this case, what would be a travel bucket list up without any trips? So I want to make that prominent. And last thing I'll say about the toolbar, just like with the top bar, it's best to keep it native. We should say it together. Adding extra stuff like a background doesn't really, you know, it competes with your content. It competes with functionality, with morphing effects. And later, Kurt will talk a little bit about that, too.
So up to this point, I've focused on the structure of the app and how people will move through it. And now it's time to bring content into the screens and decide how we're going to present it. This is what I mean with layout. I'm going to show you a couple examples of two layout patterns that I like to use, lists and collections.
Both are highly flexible, and you're more than welcome to choose one or the other, but you're going to figure it out soon that depending on the content that you want to display and what people need to do with it, there's one option better than the other one. So for example, I use lists when the content is text-based, yeah, and multiple items that we need to display, and it helps people scan through it very quickly. So that's why this layout works well for trip activities. Here, I'm using a style called edge to edge.
because I need to and when I need to categorize content I use a group style. So it's a subtle difference but the inset layout and the rounded corners really separate the different categories of content that I could have in a list. So I didn't really need this style in the app as you download it and start playing with it you're not going to find this example but I wanted to show you how I would do it because there's always some confusion around like when do you use S2S or when do you use inset. So this is how we do it.
Lastly, to help people move and act quickly through a list, I use accessories and controls. Accessories such as images and subtitles help people recognize items faster without needing to read line by line. And controls, like the selection button here, helps people take action without going into a different view, which is very common. Sometimes apps get really complex because we're adding views for just a couple actions that we could just get done from our list view.
There are many types of controls that you can add and support different functionalities. So I would encourage you to go and explore what's available to you and see if there's any opportunity to simplify your layout. Here we have steppers, toggles, sliders, and many more for you to try.
And that covers lists, which help people move quickly through text. But when the goal is to browse through photos, videos or products, collections are a much better fit. In SwiftUI, collections are essentially stacked view with a scroll view that allows content to extend beyond the screen. And it invites people to scroll and discover what's inside. Back here in the wishlist tab, the primary goal is to browse. So I choose to build a layout with multiple variations of collections. That gives trips a more visual presentation and feels personal and exciting, showcasing photos that people uploaded.
Now, keep in mind that with collections, not all content is visible at once. Most of it is off the screen. So to set the right expectations and encourage people to scroll through them, I'll make sure to add titles and image effectively. What does it mean? Well, in this example, do you think the images add value?
Does it look a little bit random? Yes, very random. And that's the thing, that sometimes we lose credibility in the content because the images doesn't just make sense. It doesn't look curated. And then we can add titles that show consistent length. Otherwise, again, the layout will start to look a little bit clunky and it looks misaligned. So not only it hurts the collection, but also the vertical scroll. Whenever I have something underneath, now everything looks like it's a little bit wobbly. So it's nice and clean to try to set some rules for you in the content of your collections.
Okay, so SwiftUI already handles many design principles that I have been sprinkling a little bit throughout my explanations, but things like hierarchy, alignment, and proximity, those are all part of the components, but also we encourage you to start exploring those to create your layouts, and that is a big part of visual design.
So we're going to use text, color, and again imagery to get attention and express your app personality. So far, we've been quite functional, trying to be effective. And now let's see if we can bring a little bit more of your charm. So I'm going to focus on three aspects of visual design that have the biggest and earliest impact-- textiles, semantic colors, and consistency. Together, they make the app enjoyable and easier to understand and something that I love, they'll give you guidance to reuse patterns as your app grows.
I'll start with textiles. So they set up hierarchy and encourage readability across different screen sizes and conditions. So I'll use system textiles. And you get that hierarchy right out of the box. What is important is that you choose the right text style for your purpose. In the app, I use this consistently. For example, Title III is for all section titles, like Summer Glow. It makes every screen look more balanced and more polished because there are concrete sections of content and the moment that I see a textile that I know which hierarchy represents, I know exactly what the content is going to mean. Another reason I rely on system textiles is dynamic type. So lots of people use larger textiles because they need it for comfort or sometimes for both. And when you use dynamic type, those textiles are named the same, but they are consistently larger. So please use it.
If you are a font fan, you'll notice that I've used Apple System Font, SF Pro, across the entire app. But I'm obviously not just using one style. SF Pro includes different variants that let me shape the personality of the app without compromising legibility. We were going for a more sporty type of vibe, so I've been using Expanded for emphasis in titles and condense only sparingly in some overlays.
Here again, I want to keep my choices minimal so both the UI feels intentional and the app is easier to maintain and grow. Then we're going to get back to our textiles and it's like, "Oh, when do I use expanded? When do I use..." And now we have a bunch of textiles, a bunch of variants, and a hell of a lot of fonts.
So earlier when I explained corrections, I mentioned that imagery carried a lot of the app's visual weight. So semantic colors are a good complement of the UI, and they have a very similar role, but much more specific, which is status and feedback. For example, I use color indigo across the app. And here, for example, it's in Completed Activities and in the Selected tab in the top bar. I avoid using it or any color that is really similar to indigo for decoration. Otherwise, people might not understand, like, is this interactive or does it have any meaning?
I said this color is named Indigo, and before that we are describing semantic colors. So it's not a color that is, that is a random, that is, yeah, I chose a random. I provide, it's provided by the system, and each color in the native, sorry, each native component has its own color. So I didn't set the background, I didn't set the separator color. All of these are already out of the box for me. The only thing that I chose was this accent color.
Because these colors automatically adapt to light and dark mode, also liquid glass and different screen environments, it's best not to get overboard again with customization, especially with buttons and controls. Probably you've seen in the app that the color palette also includes a neon green. And I reserve it only for emphasis, like in progress indicators and sub-headlines. Again, trying to perceive meaning and these visual metaphors. So the color is not semantic. You're not going to find it in our kit. So I make sure that it has enough contrast. I test it against different backgrounds. And I also provided a value for increased contrast.
If you decided to use a non-system color, just make sure to follow some simple rules, just as this one, keep testing, and just make sure that your app is still expressive, but without affecting usability. I'm getting closer to the end here, and that means that it's a good time to make sure everything looks at best. And for my final touch, I'll go back and make sure that all elements are lined up and consistently spaced out.
This makes the design feel more polished and visually balanced. It looks professional. And it also supports how people take in information and decide what to do next. If you have a lot of components cramped in little space, what happens? We feel stressed. We don't have a space to think or time to think. When we make the UI breathe a little bit more, that's when a great experience starts to happen. We're giving people space to process.
So when using native components, I'm careful to use them also in the same way across different screens. I avoid giving people multiple ways to do the same thing, which sometimes happens. We get excited creating a new screen, and we start from scratch. But the best part of it, not only to simplify development, but the experience that people are going to have is to repurpose these components. They know how they use-- they know where they've seen it before. they know how they behave, and they know exactly what is expected from them.
So progress indicators, for example, here, use the same accent color and the same shape, but they are a little bit smaller still. They are in a context that I know, yeah, what did they do. And that consistency also helps with your development. You have less components to build.
I briefly touched on imagery while explaining collections, but they are of course part of visual design. So when selecting your images and illustrations, place them side by side. And look if they are familiar in lighting, level of detail, and overall mood. Then you can tweak and adjust so they feel like they are part of the same brand. If they feel like they belong in the same collection, follow your gut, they are probably right.
In Wishlist, for example, people will upload their own photos. But again, to illustrate the point, I've used images to show you how you can also have it in the code. You're going to have access to those images so you can see the pattern that we went through. So I'm going for dynamic skies. Often the subject is smaller or interacting with the space. But the idea is that the UI is more prominent than the photos. But definitely something for you to evaluate to get a consistent look in your apps.
So stepping back, I can't really take a lot of credit for the design of the app, maybe a little bit. But CPI is handling a lot of the design decisions for me, and that is like, yes, an honest thing to say. But it's true, I really enjoy working with engineers in creating this app, and it's so cool to see that everything that I have designed, it has a very similar translation in code.
So we're very excited to share it with you and that you can get to play with it. And hopefully you can take a couple of these tips for your process and reduce a little bit of uncertainty. Sometimes it happens when you wanna get creative just like at any random point or just give you a space to be creative at the beginning, make decisions, brainstorm, and then get to code.
So as you get back to work, define or assess the app structure and its navigation. Use components mindfully. This will help you make clear decisions. So at the moment that you start coding, you know exactly where you're going, you're exactly where you're building, and yeah, you'll build an app that is much more intentional. Okay. So partner with CFUI, let it do the heavy lifting, and then make your best job bringing personality, creativity, and humanity to it. Thank you so much. I hope you have a lot of fun building it and playing with the app. And that's everything on the design side. Thank you for coming. And now I'll hand it back to Leah.
Wow, that was awesome, Maho. I love how SwiftUI's system components make it easy to design an app that people quickly know how to use. Wishlist is a beautiful app, and taking a moment to pause and appreciate the thought behind each decision makes me value it even more. Whether I'm prototyping something brand new or refining an existing app, I always try to think about opportunities to leverage the system components.
Well, now we're going to take a quick break. We'll reconvene here at 11:15 Pacific for a presentation on layout in SwiftUI. I'll see you then. Welcome back. I hope you enjoyed the break and had the chance to grab some refreshments or stretch out. Now, my colleague Kat will share a guide to layout in SwiftUI. Please join me in welcoming Kat to the stage.
A guide to layout in SwiftUI
Hi, everyone. My name is Kat, and I'm a development tools engineer here at Apple. 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. Woo! 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's 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's 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 re-rendered. In 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.
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 @State property wrapper. It means 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 Swift UI 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 a 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. [Transcript missing] [Transcript missing] Colorview 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.
at least enough space to show an ellipsis. Text will claim only the space it needs for its content. [Transcript missing] 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. [Transcript missing] 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 subview's ideal size.
[Transcript missing] This is a special and unique behavior of SwiftUI compared to other view systems. Proposals don't just go down, they also go up. [Transcript missing] 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, 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. Thank you.
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 image view. The height of 220 here is just an example of a fixed height frame. The image view 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... 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!
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 layout simple and composable. Thank you. [Transcript missing] 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 p1s are the highest priority. But in this case, it's the opposite. The higher the number, the higher the priority. me. In the Wishlist 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 trip's 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. 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 0 to a higher value, like 1. 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's 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 HStack inside a view that fits view. View that fits evaluates its subviews in the order you provide them to the initializer.
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 views in the HStack would truncate, View That Fits will choose the text view. I'm sure these changes will make our designer Maho happy.
Next are 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 HStack can also be vertically aligned at their tops or bottoms, and also on first text baseline or last text baseline.
[Transcript missing] 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 HStacks alignment to first text baseline, I bring the show all up to the same visual line established by the subtitle.
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.
Use view modifiers such as frame or position when the desired layout can't be implemented in an adaptive, flexible way. Thank you. 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. 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 in 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. Thank you.
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. This code 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.
That was awesome, Kat. I love the tip about how to get more experience with layout, trying to recreate an existing view, try things out, and build your mental model. Next, my colleague Kurt will dive into the details of Motion in SwiftUI. Please join me in welcoming Kurt to the stage. Thanks, Leah.
SwiftUI in motion
Hi, I'm Kurt. I'm a technology evangelist on the Worldwide Developer Relations team. And I am so thrilled to be with you today and to welcome all the folks online, too. Before joining Developer Relations, I worked as an engineer on SwiftUI, where I focused on navigation and API design.
Today, I'll talk about the foundations for introducing motion in your apps using SwiftUI. As devices have become more powerful, interfaces have grown more dynamic. This motion makes devices feel more alive and personal. When done well, motion makes apps and platforms easier to understand. It provides context and guides people's attention. Plus, it's just plain fun.
A lot comes together to make these dynamic interactions. There are transitions where people move from one view to another, like pushing into a view in the settings app or presenting a sheet of focus options. And gestures where people directly interact with the device, like zooming into a map to see more details or swiping between apps. And finally, animations, where an on-screen object moves, grows, or changes visual properties, like live activities in the dynamic island. These all work together to create a fluid, interactive interface. SwiftUI is the best way to incorporate fluid motion in your apps.
Motion in SwiftUI spans the gamut. At the simplest, you get automatic transitions just by using the built-in SwiftUI components. level up with state-driven animations, where you can choose from a menu of modifiers to tell SwiftUI how to animate when particular properties change. Or go all in with fully custom animations, where you specify exactly what should happen frame by frame.
Today, I'll take you through examples from across the spectrum so you can choose the right tool to create the dynamic moving experience that's right for your apps. Many SwiftUI views provide smooth motion effects automatically. I'll share some examples from the Wishlist app and show how you can tweak these automatic effects to invoke the feeling you want in your apps. Then I'll discuss how you can add state-driven animations to your app using SwiftUI modifiers and properties. I'll present the foundations of these animations so you're equipped to choose the right technique for the task at hand. Finally, I'll illustrate the power of custom animations in SwiftUI and point you to some resources for learning more.
When you use built-in views in SwiftUI, you get many motion effects automatically. This wishlist app is a great example of this power. Navigation stacks push when someone taps a trip thumbnail. Symbols like check marks acknowledge interactions. Details pop back into thumbnails. Sheets animate into view. Pop-up menus morph out of liquid glass buttons. Scroll views automatically accelerate when people swipe and decelerate when they stop swiping, optionally stopping on particular views. I'll go through four different SwiftUI views you can use to get these motion effects automatically, starting with Navigation Stacks, like this one in the Wishlist tab that presents trip details.
The Wishlist app uses a navigation stack inside each tab. Inside this stack are all these tiles for the various trips. I'll focus on a "For Each" that iterates over all the trips in a collection, like my fall getaways. Each of these thumbnails is drawn by a navigation link that provides a destination view plus a label. The label is the part of the interface that a person taps on.
When they do that, the destination view is pushed onto the stack. By default, it slides in from the trailing edge. When they tap the back button or swipe on the display, the destination view animates away. Now, some apps, like music and news, for example, refine these pushes and pops using a technique called zoom transitions.
There are just three steps to tell a navigation stack to use a zoom transition. First, add a namespace to the view's properties. A namespace is a unique identifier the SwiftUI can use to associate two other pieces of code. Second, add the navigation transition modifier to the destination view, passing the zoom parameter, a unique ID, and the namespace. Finally, add the match transition source modifier to the links label.
Now, the Wishlist app uses a Zoom transition when I tap on the image for a trip. When I navigate back, the detail view shrinks back into the thumbnail. Zoom transitions work great when the destination repeats content that's included in the label, like this beautiful picture from Zion National Park.
Sometimes, the label of the navigation link is more detailed, like this one from the search tab in Wishlist. It shows a thumbnail photo next to the trip's name. In a case like this, add the matched transition source to a subview of the links label. Here's how. These rows are drawn with a custom view named Search Item View. It uses an H stack containing the thumbnail and the trip name. Here I added the matched transition source modifier to the image thumbnail instead of the whole search item view.
I pass the namespace in from the surrounding navigation stack as a property. And now, when I tap the row, the detail page zooms out of the thumbnail. When I pop back, the page zooms back into the thumbnail. I just love this little touch. It's fun and it draws a person's eye to the row where they started from.
The second kind of built-in view I'll cover today is sheets. I'll talk through the code for a sheet in the Wishlist app. Here's the navigation stack for the Wishlist tab again, with the property isPresentingAdTrip that I'll use to determine if the sheet is showing. Inside the stack is a scroll view with a toolbar modifier attached. Inside the toolbar is this plus button. With that structure in place, there are just two steps to present a sheet. First, the button in the toolbar sets the isPresentingAddTrip property to true. And second, the sheet modifier specifies what should be shown when isPresentingAdTrip is true.
The modifier here takes a binding to the property. That's what the dollar sign means. Cole will go into more details on binding in a bit, but the important point now is that this lets SwiftUI set isPresentingAdTrip back to false when someone dismisses the sheet. With these in place, when I tap the button, the sheet slides up from the bottom of the screen. When I tap the close button, it slides back down.
Just like I did for navigation, I can apply my three-step process to convert this to a zoom transition too. So first, I add a namespace to the view's properties. Second, I add the navigation transition modifier to the sheet's content, passing the zoom parameter. And finally, I add the matched transition source modifier to the button label. Now, when I tap the button, the sheet morphs out of it. When I dismiss the sheet, it morphs back home. This motion helps people associate the button with the sheet.
SwiftUI scroll views also provide built-in motion effects. Scroll views move when people swipe on them with smooth deceleration and a friendly bounce at the end of the content. Here's the scroll view for showing the badges I've earned in the Wishlist app. Scroll view takes a sub view showing the content that people can scroll through. Here that's a stack of the tiles showing all of my badges. Optionally, scroll view takes a parameter specifying what directions it can scroll in. So horizontal here.
For the badges in the Wishlist app, I want the scroll view to always stop with the badge centered on the screen. With just two steps, I can tell SwiftUI to do this. First, I add the scroll target behavior modifier to tell SwiftUI to stop aligned to a view. Then I add the scroll target layout modifier to tell SwiftUI which stack of views I want to participate in this behavior. With that in place, now when I swipe on the badges, the scroll view always stops with a badge centered on the screen.
It would be fun to add some whimsy to these badges as they scroll in and out of the view. Apps like books and sports use these sorts of motion effects. They do this with the scroll transition modifier. Apply the modifier to the view that you want to transform. Here, that's my achieved goal tile. I make the transition interactive, so it responds to manual scrolling, and set the direction to horizontal. The modifier takes a closure defining the transition. SwiftUI passes a proxy for the content plus an animation phase to the closure.
The phase specifies how far along the transition is. Minus one for completely scrolled away on the leading edge, zero for completely on screen, and plus one for completely scrolled away on the trailing edge. Use the phase to modify the content. Here, I'm scaling down the goal tile when it moves off screen and applying a 3D rotation along the vertical axis.
Check it out. Notice how the views seem to wrap at the edges, as if they're part of a round carousel. It's good stuff. The last kind of built-in view I'll cover today is an image view with SF symbols. Like Maho said earlier, SF Symbols is a library of iconography with more than 7,000 symbols. There are no duplicates on this screen. SF symbols are designed to integrate seamlessly with San Francisco, the system font on Apple's platforms.
The SF Symbols app for Mac, available from the Apple Developer website, allows you to explore the library and find the right symbol for your use case. For example, the Wishlist app uses a checkmark in its activities list. To add the checkmark in my own app, I add a SwiftUI image to my code. In the SF Symbols app, I control-click on the symbol I want, checkmark, circle, fill, and choose Copy Name.
Then in Xcode, I paste the name into my image declaration. And there's my check mark. SwiftUI provides several different ways to animate SF symbols. I'll share three examples. use an indefinite effect to indicate an ongoing operation, like waiting for someone to send a reply in a chat. These effects run continuously while active. Some examples are variable color, scale, and breathe effects. To add one of these effects to an SF symbol, use the symbol effect modifier with an isActive parameter. Whenever this argument value is true, the effect will run.
Use a discrete effect for indicating an event, like a download completing. These effects are triggered when the event occurs. Some examples are bounce, pulse, and wiggle. To add one of these effects to an SF symbol, use the symbol effect modifier with the value parameter. Whenever this argument value changes, the effect will run. use a content transition effect when replacing one symbol with another, such as when adding a check mark to a circle.
To add one of these effects to an SF symbol, use the Content Transition modifier with the Symbol Effect type. The transition will run whenever the chosen symbol changes. The SF Symbols app also lets you experiment with animations. Here, I switch to the animation inspector, select a breathe animation, and set it to run once. I click Play to test it. Then, I copy the animation configuration using the Copy menu.
Back in Xcode, I paste the modifier with the options I configured in the SF Symbols app. and breathe in and out. SwiftUI's built-in views like stacks, sheets, scroll views, and images give you a variety of built-in effects automatically. Add modifiers to refine those effects for your app. Next, I'll introduce another kind of animation in SwiftUI, state-driven animations. I'll explain.
Earlier, Leah shared how SwiftUI views map from data to pixels on screen. And Kat showed an example of a view being shown or hidden in the wishlist layout based on the value of a property in the data model. Data that's used this way is called the view's state.
Here's an example from the Wishlist app. This is a trip details view with a list of my planned activities at the bottom. Floating over this image is this text that shows what percentage of my activities I've completed, plus a bar that fills in as I complete more of them.
When I check off an activity, SwiftUI updates the associated state to record that the activity is done. The views that depend on this state automatically update when the state changes. Thank you. For now, all the changes happen instantaneously when I check off an activity. I'll tell SwiftUI to animate this instead. Do that with the withAnimation function. Here's how. Here's the code for the button that represents an activity on the trip details page. The action of this button toggles the isCompleted state of the associated activity.
I wrap this toggling action in the width animation function. This tells SwiftUI to animate any view updates that happen as a result of the wrapped state changing. Here's the update again. Now, when I complete an activity, the percentage text crossfades, the bar fills, and the activity color and checkmark crossfade. To animate these changes, I just added with animation. The rest happens automatically.
The power of width animation is its simplicity. Just wrap a state change in the function, and every property of your views that depend on that state and can be animated will be. On the other hand, some parts of your app might need a more precise approach. To describe that, I'll focus on just this completion overlay from the Wishlist app.
In the app, these three views, the percent complete text, the bar, and the subtitle, are embedded in a custom activity progress view. I'm going to leave out a few styling modifiers plus the subtitle to focus on the animations. Activity progress view takes a completion value property between 0 and 1 describing the percent complete. A text view with formatting renders the completion percentage. This opacity modifier hides the text when the completion value is zero.
I use a rounded rectangle for the lime green bar. The width of the bar is set by multiplying a constant bar width by the completion value. And again, an opacity modifier hides the view when the completion value is zero. Now, the design for Wishlist calls for animating the activity progress view wherever it's used. I can use the animation modifier for that.
By applying the animation modifier to this VStack, I tell SwiftUI to animate these views whenever the passed in completion value changes. Here's how that behaves so far. It's exactly the same animation as I got using with animation. The animation modifier is an alternative to using with animation at the point the state changes.
With animation is a good choice when you want some, but not all, sources of a state change to trigger an animation. On the other hand, use animation modifier when you want a view to always animate, regardless of the source of the state change. Thank you. Both with animation and the animation modifier let you change the timing of an animation, its velocity, acceleration, and bounce. Use this to take more control over the feel of the animation.
For example, it would add a touch of celebration if the bar bounced a bit when it increased. I can do that by passing bouncy to the animation modifier. Here's what that looks like. The green bar oscillates just a bit before settling to its new value. I can even add some extra bounce. Check out what happens now. The bar has a fun bounce. There's something wonky with the text. So I'll run that again and freeze during the animation.
My extra bouncy animation shoots past the target value, then retreats towards the original before finally settling. And with the numbers, this retreat is far enough that the digits start fading back to their starting values. I can solve this by adding a second animation modifier. I add a smooth animation to just the text view. Now, the bouncy animation applies to the whole VStack, but I override that animation just for the text subview. Now, the glitch with the numbers is gone, the bar bounces, but the numbers crossfade smoothly.
Now that that's sorted, I'll dial the bar bounce back a bit and add one more motion effect to the text. - Next. The content transition modifier with the numeric text argument tells SwiftUI to use a special counter animation when the numbers change. Now the digits roll up in order, calling attention to the change. I love that one.
Summing up, SwiftUI views map from data to pixels. State-driven animations let you control the behavior when the pixels update as the result of this data or state changing. specify how those animations should behave by either wrapping the actual state change in the withAnimation function, or by adding an animation modifier to the views. Now, before moving on, I'll share just a bit about the optional parameter I used to control the bounciness of the bar animation. This optional parameter controls the timing of the animation. Here's what I mean. I used the bouncing animation for the bar. The animation overshoots its destination before easing back. It's the most playful of the built-in animations.
on a plot of value versus time, the value overshoots before settling to its final level. On the other hand, the snappy animation moves quickly to its final value, giving a feel of precision and professionalism. Thank you. The plot shows a distinct knee where the value reaches its final level.
The smooth animation sits at a sweet spot between bouncy and snappy. It settles quickly but feels more casual than a snappy animation. It has a gentler bend and approaches its final level more gradually. Smooth is a great general purpose animation. In fact, it's the default on all of Apple's platforms.
Finally, use the spring animation to customize all the properties of a spring. This gives you fine-grained control. Tune the bounce to control the playfulness of the animation. At a bounce of 0.6, the value oscillates multiple times before finally settling. You can also tune the duration of the spring and even how it interacts with other spring-driven animations.
Typically, one of the named springs, bouncy, snappy, or smooth, will be appropriate. But it's nice to know you have control if you need it. For more details on how animations in SwiftUI work under the hood, check out Explore SwiftUI Animation. Then bounce over to Animate with Springs for technical and design guidance on animation timing. Both videos are from WW23.
I've shared how the Wishlist app uses SwiftUI's built-in views to get beautiful motion effects automatically, and how the app uses state-driven animations to add an extra touch of polish. I'll wrap up with a couple of examples of custom effects built with SwiftUI. These examples aren't in the Wishlist app today, but I'll explain how you can add them. Experimenting like this is a great way to get a feel for what's possible in SwiftUI. My first example is a bouncing ball. This might work as a loading indicator. Implement this using a phase animator. This animation has four phases. First, the ball drops.
Then it squishes. That's a technical term. The ball expands again, and then it rises. This cycle repeats. An enum is a great way to define the phases for a phase animator. So drop, squish, expand, and rise for this example. Then add properties for each of the attributes you want to animate. For this example, first add the y offset. The properties return the value at the end of each phase. At the end of the rise, the ball is 40 points higher than-- Next, add a property for the ball's scale. When squished, the ball is a bit wider and a bit shorter than its normal shape. Otherwise, it's round. After defining the phases, add a phase animator to a view.
Make the animator iterate over all the phases by conforming your enum to case iterable and passing all cases to the phase animator. SwiftUI will call the first closure of the animator passing each phase in turn. So drop, shrink, expand, and rise. Next, draw the animated view. Here that's the circle. Then apply modifiers, reading the arguments from the phase. Here I'm offsetting by the phase's y offset value, and scaling by the phase's scale value.
Finally, in the second closure, define the animation that should be used during each phase. Here I'm using ease in animations for drop and expand. These are slow at the beginning and fast at the end. And I'm using ease out for squish and rise. FOR MORE INFORMATION ON THE Those are the four steps for building a phase animation in SwiftUI. Define the phases. For each animated property, define the value at the end of each phase. Draw your view applying animated effects. And finally, specify the animation for each phase. To learn more about phase animators, check out Wind Your Way Through Advanced Animations in SwiftUI from WW23.
The second custom effect I'll share is a transition to celebrate a person earning a badge in Wishlist. This effect is cool, but fast, so I'll play it again. SwiftUI can apply a transition when you add or remove a view or replace one with another. This badge transition has two major steps. First, write code to replace one view with another. Here, I'll start with a grayscale version of the badge and then replace it with the colored version. Create a group to hold the before and after images. Inside the group, use an if to show the colored image, if the badge is earned, or a grayscale copy of it otherwise. Now setting is earned to true will replace the gray badge with the colored one.
The second step is to animate this replacement. To do this, add a transition modifier to the group. This tells SwiftUI to apply a motion effect when the views inside the group are swapped. There are some built-in transitions that you could use here, like opacity or scale, but to make the badge flip, use a custom transition.
Create a custom transition by declaring a struct that conforms to the transition protocol. The transition protocol has just one requirement, a body method. Like the scroll transition modifier I shared earlier, the body of a custom transition takes a content parameter. This is a proxy for the view that's being added or removed. The body also takes a phase, which tells you whether the view is appearing or disappearing.
Inside the body, apply effects to the content proxy using the phase as a condition. The phase has a property "isIdentity" that's true when the view is visible. So to flip the badges, apply a rotation 3D effect with no rotation when phase is identity, Otherwise, 180 degrees positive or negative, depending on whether the view is appearing or disappearing.
Apply an opacity modifier to gradually hide or show the badge during the flip. With the custom transition defined, pass an instance of it to the transition modifier. Now, when I toggle is earned inside a width animation block, the gray badge animates out and the colored badge animates in using the custom flip transition.
To learn more about creating custom motion effects in your apps, I recommend the video Create Custom Visual Effects with SwiftUI from WW24. And for design guidance, check out the motion section of the human interface guidelines on developer.apple.com. As you consider where to use motion effects in your apps, use SwiftUI's built-in views and controls. These give you a variety of motion effects automatically. Add modifiers to refine those effects for your app.
use state-driven animations to react to events and guide people's attention to what's important. Set your app apart with a carefully chosen set of custom animations. Thanks for joining me on this tour of motion effects in SwiftUI. I hope it's helped you understand what's possible using SwiftUI. Now, please welcome Leah back to the Big Sur stage.
That was awesome. It's so cool how SwiftUI makes it easy to add personality to an app through motion. I loved the green bouncy phase animation. That was awesome. Up next, we'll take a break for lunch. So take some time to enjoy the refreshments, ask a question, or meet someone new. We have a full agenda in the afternoon, and we'll meet back here at 12:15 Pacific.
Welcome back. I hope you enjoyed the break. Oh, yeah, let's do it. Clap it up. Thank you. I don't get to do this every day. I hope you enjoyed the break and got the chance to ask a question or meet someone new. We have a full and fun afternoon in store. Up next, my colleague Cole will dive into the details on SwiftUI Dataflow. So please join me in welcoming him to the stage.
Flow data through SwiftUI
Good afternoon, everyone. My name is Cole, and I'm a core technology evangelist here at Apple. And today, I'm going to discuss data in your app and how to flow it through to your SwiftUI views. The Wishlist app is a great example that I'll use throughout the day to demonstrate some of the ways that you might encounter data flow in SwiftUI apps. The Wishlist app does a lot of the same things with its data that your apps may need. And it takes different approaches depending on the type of data in question.
I'll explain some of the most important ones. First, there are certain places in this app where the state of the interface will change when someone interacts with it. For example, when someone's modifying a trip, the app will need to keep track of whether the UI is in editing mode or not.
Now there's a few different places where this will come up, like keeping track of when a sheet is shown after tapping a button, or when an alert needs to be displayed. I'm generally going to refer to these cases as view state. [Transcript missing] So I'm going to need to model this data in the app and flow it through to all of my SwiftUI views.
The app will also need to keep track of preferences that someone sets in the app. For example, the app has a sort button on the list of activities on a trip. You can sort by title, by date, or whether the activities are completed. The app will need to keep track of the last sorting method that someone chose so that it uses that same sorting method the next time this view is shown.
And finally, I want to build an app-- sorry, I want to build an approach for persistence. Persistence is an approach that lets an app save its data to the device's storage, like in a file. That way, the next time someone comes back to the app, all of their changes are still there, even across relaunches of the app.
So in this session, I'm going to discuss each of these use cases more in detail. This includes data for view state, building a rich data model, handling preferences and configuration, and a few techniques for persistence. By the end of the session, you'll be equipped to reason about each of these use cases, and you'll know patterns that you can replicate to address similar needs in your own apps. So first, I'm going to start with ViewState.
As I mentioned earlier, sometimes an app just needs to create a piece of data to track the state of the interface itself. These things are often temporary and safe to reset if the view disappears. For example, the sample app tracks whether or not a trip is in editing mode or not. If someone navigates away from this trip and then comes back to it later, it makes sense that the UI is no longer in editing mode, even if I didn't tap done.
Here's another example of state that needs to be tracked in the interface in this app. On the wish list tab, there's an add button in the toolbar. Now Kurt talked about this button earlier when refining animations. Tapping this button makes a sheet appear where someone can start entering the details of a new trip to add to the app.
So the app will need a piece of state to track whether or not the sheet is currently shown on the screen. Also, the sheet itself has buttons to save changes or cancel adding a trip. So those actions in the sheet will need a way to update the state so that SwiftUI will dismiss the sheet.
So here's how this Add button is implemented in SwiftUI with the sheet. In the body of WishlistView, there's a SwiftUI button with a symbol label, and I'll need to add some code to the button action to show the sheet. Thank you. To present the sheet itself, I need to use the sheet is presented modifier, which is added to the view.
Now, before I can complete this code, SwiftUI needs me to make a decision. What is the data that tracks whether or not this sheet is actually shown on the screen right now? Well, this is a great use case for using the @State API. Here's how it works. I'll create a new property called isPresentingAdTrip and decorate it using @State. And it defaults to false. Using @State tells SwiftUI to create a new piece of data that lasts as long as this view does. I'll actually say more about the lifetime of this value in a bit.
For now, now that I've defined that, I can use it in the rest of my view. In the button action, I'll set isPresentingAdTrip to true, since tapping the button should always make the sheet appear. And I'll disable the button if the sheet is already being presented by passing the state to the disabled modifier. Note that because the view body reads the isPresentingAdTrip value, like in this modifier, the wishlist view establishes a dependency on this value. Any time isPresentingAdTrip changes, SwiftUI will update this view.
I'll also pass the isPresentingAddTrip property to the sheet modifier. This dollar sign notation allows me to pass a binding to the state, letting this code share this value with the sheet so it can both read and make changes to it. I'll come back to explain more about bindings shortly.
Decorating a property with @State tells SwiftUI that this property is owned by the enclosing view. The value of this property exists as long as the view does. And this is an important point that I just touched on earlier. @State properties match the lifetime of the view in the interface, not the lifetime of the view struct itself. So I'll dig more into how this actually works.
As Leah discussed earlier today, SwiftUI views are just short-lived descriptions, like a template. When the wishlist view appears in the interface, SwiftUI runs its body in order to update the UI on the screen. But then it throws the instance away. And this repeats every time SwiftUI renders the view.
So why doesn't the isPresentingAdTrip property get reset any time this view gets reinitialized, since the default value is false? Well, that's because SwiftUI gives special treatment to @State properties, like this one. Behind the scenes, SwiftUI has its own data storage for all of the state owned by views. When SwiftUI instantiates this view for the first time, it allocates space in its internal storage for this property, alongside all of the state owned by other views. This storage is indexed by the view's identity.
Think of the identity as corresponding to the lifetime of this view's rendered results on the screen. So the first time SwiftUI renders the view, it assigns it an identity. And when someone navigates away from this view, SwiftUI deletes the entry from its storage. If they come back to the view later, SwiftUI assigns a new identity and allocates a new entry in its storage.
So here's how this will work with my button that will show a sheet when tapped. The first time someone navigates to a wish list view, SwiftUI allocates storage for this view's isPresentingAdTripState property using the default value of false. Then it instantiates the struct and sets the value of isPresentingAdTrip to match SwiftUI's internal storage, and then runs WishlistView's body to render to the screen. And then it discards the view instance.
When the person using the app then taps the button, the action closure runs, setting isPresentingAdTrip to true. But because this is an atState property, this assignment modifies SwiftUI's internal storage. And because the body of the view depends on the state, SwiftUI triggers an update, creating a new view struct, populating the value of isPresentingAdTrip with the new true value from internal storage before running body.
So this internal storage is how SwiftUI maintains state for the lifetime of the view in the interface, even though the structs themselves are transient. Okay, so this internal storage explains how this @State property asks SwiftUI to track a boolean for the lifetime of this view in the interface. But @State isn't just useful within a single view declaration. It can also be shared with other views using something called a binding. This dollar sign in my sheet modifier allows me to access a binding to this isPresentingAdTripState.
A binding is just a read/write reference to some piece of state in your app. They're great when you're creating a view that needs to read a value from its enclosing view and might need to change that value too. Bindings are used all over SwiftUI. You'll find them in the parameters for toggles and text fields and the sheet modifier and more. These are encapsulated controls that depend on this data and can modify it when needed. For example, here's a snippet of the Add Trip view, which is the view inside the sheet itself. It uses @Binding to receive a reference from the enclosing view to the state that tracks whether the sheet is presented.
In the view body, there are buttons that can cause the sheet to be dismissed. In order to do this, they set the value of the isPresented binding to false in their action closure. That causes SwiftUI to update all of the views that depend on the state, like my enclosing view, and then leads to the sheet being dismissed.
Places like my sheet where I just want to track when someone interacts with the interface is a great fit for @State. It's temporary data that should reset when the view goes away. Or when you have objects that should only exist as long as the view does. But @state isn't the best fit for other types of data, like my data model. For those cases, you'll want a way to express types of state to SwiftUI that's owned by other parts of your code, not owned by the UI itself.
So that leads me to my next use case, which is my data model. In Wishlist, the data model is the heart and soul of this app. It includes all of the information that people want to come back to the app for, like the names and photos and activities on each trip. Like in this example, the trip object has the name Peru Off Road and has this gorgeous photo associated with it. There are also relationships between these objects. For example, a trip can have multiple activities. And activities have their own attributes, like a name and a Boolean to track whether the activity is complete.
So I've created a data model using classes to represent trips and activities in this app. Each class has properties to store attributes like name, photos, and dates. They also store references to each other in order to model the relationship between these objects. For example, the trip object can have any number of activities associated with it by storing references to them in the activities property. And these objects are also editable, as people might change these properties in the UI, like when someone edits the name of a trip in a text field.
I've also created a class called DataSource to manage all of these objects in the app. It's a single place where the app can look up all of the objects that it needs to show in the UI, like getting a list of all of the trips. So having a data model like this is a great start. But to really use this data model in SwiftUI, I do need to add one quick thing. The data model needs to be able to know when changes to its properties happen, so that SwiftUI can update the UI.
And this is what the observable macro is for. With this one line of code, you can give SwiftUI the power to establish dependencies on the properties in your classes. The Observable Macro adds update tracking to each of the class's properties. To use it, just decorate each class with the @Observable macro.
So now that the data model is observable, SwiftUI can now establish dependencies on model properties directly. In this example, the trip card view takes a reference to the trip object it should display. Note that there's no @state or @binding. In fact, there's no decoration at all on this reference. What makes this view work is that inside the view body, it reads the trip's photo URL property. This tells SwiftUI that the trip card needs to update any time the photo URL property changes on this observable class.
And this also works through computed properties. For example, let's say I want to use a placeholder photo for trips that don't have a photo saved yet. To handle this in the UI, I could define a computed property on trip called photo URL or placeholder. This computed property returns a placeholder photo if there isn't one defined for the trip.
And then I use that new computed property in the view body instead of the photo URL. SwiftUI is able to determine that the underlying property, photo URL, is read in this view body when the computed property is accessed. So SwiftUI will update TripCard anytime the underlying photo URL is changed.
So to recap, this app uses a structured data model of trip and activity objects. It uses reference types or classes to model each object, so that I can use references to model the relationship between these objects. And it's easy for these properties to be editable anywhere in the UI. To make data in these classes flow through to SwiftUI, I just add the @Observable macro to each of their definitions. Then, my SwiftUI views can just read the properties directly in view bodies.
Now there's one more thing I need to do. What I've shown so far works great once a view already has a reference to an object, like a trip. Now, I said earlier that all of these objects are owned by the data source class. But how do I tell my views which data source instance to use?
Well, I've actually already discussed one of the ways that you can do this, and it starts with @State. Recall that @State will create an object that exists as long as the view does. So, at the top of my app, I could create an @State property that holds a data source object. Putting @State in this app declaration creates an object that exists as long as the app does, and then it can be passed down to the views.
Now, this might work well for very simple view hierarchies. In this app, different parts of the UI may need to get access to the data source, for example, to get the full list of trips. And with a very simple view hierarchy, it's perfectly reasonable to just pass an object like data source down to the views that need it.
But as an app grows in complexity, this approach might start to become cumbersome. An app with many nested layers of views may end up needing to pass and store a reference to the data source just so it's able to pass it along to the deeper views that need it.
So to avoid this problem, cases like this can be a good fit for the environment. Think of the environment as the essential properties which configure parts of your app. And they don't change very often. In this case, I'll create a data source object as state, and I'll set that object on the view's environment. This configures the views that need a data source with this particular instance.
This is safe because the data source object itself won't be swapped out very often. It's observable, and there are lots of different views in my app that may need a reference to it. So by putting the data source in the environment, any view that needs a reference to it can just grab one directly.
The environment flows through the entire view hierarchy that it's applied to. A view just has to ask for a value from the environment and SwiftUI will provide it. For example, deeper in the view hierarchy, the recent trips page view gets a reference to the data source by decorating a property with @environment. SwiftUI will populate the value of this reference with the object of a matching type from the environment.
Since data source is observable, a view can then establish dependencies on its properties, right from within the view body. Here, the recently added trips property is red in this view body. So the recent trips page view will update any time a new trip is added to the app.
So ViewState and my data model cover a lot of the data flow that I need in this app so far. But there's a couple of other use cases that take a slightly different approach. And so I'll continue with talking about preferences and configuration in this app. I mentioned earlier that when someone taps on a trip, they can sort the list of activities that appear by name or by whether they're completed.
The app will need to store this preference so that anytime someone comes to this view, it uses their preferred sorting method. Now preferences aren't a great fit to go inside the data model that I discussed earlier, because this doesn't really have anything to do with the trips themselves. It's something that tracks how people prefer to use different features of the app.
So to build the sorting feature, I could start by defining a new piece of state using @state to track what sorting method is being used. In this example, it's called sort option. Then the activities subview will read the sort option when laying out its activities. Now this works, and the activities are now sorted by what someone chose in the menu.
But recall that @State properties like this one are only stored for the lifetime of the view. So if someone leaves this view and then comes back to it, the sorting will always revert to the default, which is by title. I want to make it so that anytime someone comes back to the Trip Detail view, it's sorted in the same way they selected before.
So to do this, I'll change @state to @appstorage and provide a unique identifier for the setting. App Storage works really similarly to @State. It declares a piece of state that is global to your app, and it works best with simple, codable types. App storage values actually get stored to disk automatically. Under the hood, it uses an API on Apple platforms called user defaults, which stores a set of keys and values for your app. And this is a great place for settings, preferences, and app configuration details.
So now by saving the sorting preference in an app storage property, the activities now always sort by the last option that someone chose in the view, even as they navigate across many different trips. Finally, the last use case that I'd like to discuss today is persistence. The example I just showed you where I saved that sort preference by using @appstorage is actually an example of persistence. SwiftUI will automatically use the user defaults API to get the value of this key on disk and then set the value in the view to match.
And if the sort option is assigned a different value, App Storage will save that new value back to user defaults. So even if someone were to come back to your app a day later, or a week later, or a month later, the sort option property will remain the same until it's changed.
But of course, it's not just preferences that you might want to persist. This app has this rich data model, and people are definitely going to want to keep their wish list saved for days or weeks or more. So to build a data model like this with persistence in your app, one great option is SwiftData.
SwiftData is a framework that lets you add persistence to your app quickly, with minimal code and no external dependencies. It uses modern Swift language features like macros and property wrappers, and that lets you describe your models just by writing Swift code. By default, it leverages core data's proven persistence technology, and models in Swift Data are all automatically observable.
To learn more about Swift Data, check out the videos Meet Swift Data and Build an App with Swift Data. Thank you. As I shared earlier, the data model is comprised of a few classes. And each of its attributes and relationships to each other are stored as properties on each of these classes. And currently, these classes are decorated with the observable macro. But if I wanted to make them persist using Swift data, I would just replace the observable macro with the model macro. The model macro turns these classes into Swift data models. And again, this automatically makes them observable as well.
So as a fun exercise, you can try downloading the sample code when it's available and going through a few steps to migrate this app over to Swift Data. For those of you interested in doing that, I'll explain a few of the key things you'd want to change in the code. First, you'll want to add some metadata to your model. You'll refine how data is fetched or queried from inside the app's views. And you'll set up a container to store your models in. So I'll touch on each of these steps quickly.
First, after switching the model from @Observable to @Model, you'll go through the models and annotate properties where you'd like to give Swift data more information about them. For example, in the trip model, you can use @Relationship on the activities property to define the relationship between trips and activities. Here, I set the delete rule to cascade so that when a trip is deleted, all of its activities are also deleted.
I've also set the inverse relationship on the trip property to, sorry, the inverse relationship of the trip property on activity. So this means that the trip property on these activities will always point back to the trip that it belongs to. Thank you. Now, SwiftData makes it super easy to fetch and display models within a view. By using @Query, you can just ask SwiftData for an array of models, sorted and filtered the way you want.
In this snippet, I've updated the recent trips page view I showed earlier. Now it used to use the data source object to get a list of recently added trips, but with SwiftData, I can just use @Query instead. This query asks SwiftData to provide an array of trips sorted in descending order by creation date. The view body then uses this recently added trips array in a for each. SwiftData will update this view if the query result changes, like when a new trip is added to the view.
And finally, in your app declaration, you'll configure your app with a container to store models in. By adding the model container modifier and passing the names of your model types, the app will use a default container for its models. So these three changes, adding property metadata, refining queries, and then adding a model container, are the keys to get started. And this brings SwiftData's powerful persistence to an app like this one.
So now I've covered all of the data flow use cases for the Wishlist app, and you're empowered to take similar approaches in your SwiftUI apps as well. When a view in your app just needs to track simple UI state, like when a button was tapped, use @state. And then use bindings to let other views or controls share that piece of state.
Consider using the observable macro for rich data models that you're building with reference types like classes. When you want to save data to storage so that it sticks around, use app storage for small things like settings and preferences. And consider the Swift data framework for your model data. Thank you for your time. Hope you enjoy the rest of your event. And back to Leah.
That was awesome. It's so important to take the time to learn the basics of Dataflow and when to use each tool. Up next, we have a special guest. James Graham, the CTO of AllTrails, will share about their experience adopting SwiftUI from a complex, mature UIKit app. Please join me and welcome James to the stage.
AllTrails: Momentum without a rewrite
Good morning, or good afternoon, everyone. I have a question for you. Maybe this resonates. Have you ever looked at your legacy UIKit code base, maybe a huge view controller written four years ago and thought, yikes, this needs a serious refactor. Let's just put a pin in this and rebuild the whole thing in SwiftUI. Quick show of hands, has something like this happened to you? Wow, I see a lot of hands raised. I'm sure there's more online watching.
It's a tempting thought. But at the scale we operate, a rewrite, whether it's from an engineer's effort or an AI-native workflow, it introduces a huge risk. So, hi, my name's James Graham. I'm the CTO at AllTrails. And today, I want to talk to you about how we achieved modern velocity without a rewrite. I want to show you how we let SwiftUI adopt us, rather than forcing adoption from the top down. Before we dive in, here's the shape of the story. First, I'll share what AllTrails is, our scale, and our constraints. Then, I'll talk about how SwiftUI entered our codebase without a rewrite or a mandate. After that, I'll show you the tipping point when SwiftUI stopped being an experiment and started becoming the default choice. And finally, I'll close with what things look like today and how we view our hybrid architecture.
To understand our technical decisions, you have to understand our scale. Today, AllTrails is the world's most popular and trusted platform for outdoor exploration. Our mission is simple: to help the world find its way outside. We help people discover trails, navigate confidently, and elevate their experiences on the trail with up-to-date details of the trail and features like Photo Tour, which highlights photos along the way. Whether it's a walk in your local park or a multi-day hike, we've got you covered. AllTrails has over 90 million community members, we have 500,000 trails worldwide, and our members have logged over 1.9 billion miles.
We're available in 14 languages, and this means every technical decision we make affects millions of members across different devices, different regions, and crucially, different connectivity levels. We serve a wide spectrum of members with different interests and preferences. On one hand, we have the casual member looking for an easy and relatively flat afternoon walk with a nice view of a local lake. And on the other, we have an avid hiker who's tackling an all day half dome hike, navigating offline with no cell phone service. That diversity creates strict constraints around reliability, battery life, and UI performance. We cannot ship broken code, but our app isn't static. It's constantly evolving with new surfaces and new feature depth.
By the time SwiftUI arrived, it promised the AllTrails was already a very large and mature, successful UI kit app. As an example of that evolution, here's our home page over the years. We were shipping with weekly release cycles and we were powering both free and paid experiences. And here's the most important thing that I'll say about our legacy code. UIKit wasn't a problem to be fixed. It was the foundation that enabled our scale. Because of that, we couldn't shut down the trail and make changes. We had to maintain it and we had to upgrade it mid-hike.
When SwiftUI arrived, it promised things we desperately wanted-- cleaner state management, views that automatically update and when data changes, eliminating that whole class of bugs where UI falls out of sync with the model. less code. We're talking about a 40% reduction compared to UIKit equivalents. That's 40% less code to maintain, to update, and to read.
Live previews, the ability to iterate on design changes instantly. But rewriting a mature app wasn't an option, technically or organizationally. We needed a different approach. So SwiftUI entered our codebase quietly. It wasn't a mandate or roadmap item. We created a sandbox and we used it for low risk experimentation, for prototypes and isolated services. It gave us a place to learn the framework without betting our release candidate on it. The first real decision wasn't UIKit or SwiftUI. It was, how do they thrive together? We invested early in interoperability. Take a look at this code snippet here.
This is our bridge. We take a Swift UI feature, like our trail coordinator, we wrap it in a hosting view, and we drop it right into a standard UIKit stack view. And then we add it to the scroll view. As we scroll down the page here, you can see some sections of the hosting view that contain the SwiftUI subviews. We established this pattern early. This was a process decision, ensuring the boundaries were clear and the two worlds could speak the same language.
Over time, two parallel tracks form naturally. UIKit still handles the heavy lifting for us, like app lifecycle, navigation, and complex or deeply integrated surfaces like our trail page or our community activity view. In a mature app, it didn't make sense to rewrite stable, battle-tested screens just to change frameworks. So we avoided rerouting off a well-marked trail mid-hike and focused our new investments where it delivered clear user and velocity gains. On the SwiftUI side, we intentionally are using it where it fits best, on those isolated, well-bounded surfaces, rendering heavy views, and new experiments. IN THE NEXT FEW MINUTES.
You can see two examples of that here. The Trail Review Flow is a self-contained surface with dynamic state and UI updates, which maps well to SwiftUI's declarative model. The Ask the Trail Anything experience built on Apple Intelligence is a newer, more experimental service. SwiftUI lets us iterate quickly here and evolve the experience without coupling it tightly to the app's core architecture.
Another area where SwiftUI shines very bright is our design system. Let's take a look at our app in debug mode that visualizes our design system called Denali. Denali is growing larger every day, and we've partnered across our design system, design and engineering teams to ensure all new features leverage this system. All of our core components, buttons, segments, controls, badges that you see here are part of our design system, viewable in our app in debug mode, and now built in SwiftUI.
What used to take hundreds of lines of boilerplate now take a fraction of that. And when we need to add a new variant or adjust spacing, it's a simple change that propagates everywhere. SwiftUI allows us to scale our design system without bloating our code base. We also noticed something unexpected happened when we adopted SwiftUI. It started influencing our architecture. View models got smaller, often by a third, because we stopped writing glue code just to keep the UI and the state in sync. We also wrote a lot less explicit plumbing, fewer publishers, fewer operators, and far less lifecycle management because SwiftUI handled state propagation for us.
And because UI, state, and behavior live together, changes touched fewer lines. Our UI pull requests were 30% to 40% smaller, and code reviews got noticeably faster. That's when SwiftUI stopped feeling like a UI experiment and started feeling like the right way to build systems. We never forced engineers to use SwiftUI. They chose it for their new work because it had less friction. It lowered the cognitive overhead.
When the choice is write 40% less code, adoption becomes self-sustaining. Here's the takeaway for us. SwiftUI didn't spread because we told people to use it. It spread because it was the fastest path forward. When a framework lowers cognitive overhead and removes complexity, engineers don't need convincing. We just reach for it.
But the code base, it didn't flip overnight. It tilted. UIKit remains deeply embedded. SwiftUI grows around it. We measure success metrics like reduction in lines of code per feature. [Transcript missing] We realized that interoperability is infrastructure. We invested in hosting wrappers, shared animation bridges, and unified theming. When the bridge is solid, SwiftUI stops feeling new and starts feeling foundational.
A perfect example of this is our Apple Watch app. Both our compass and map surfaces are built with SwiftUI, and our map uses MapKit. This demonstrates how we can ship critical performance-sensitive functionality using modern architecture. We chose SwiftUI here not because it was cool, but because it allowed us to iterate faster on a complex surface without touching the legacy navigation logic.
So, where are we today? AllTrails isn't migrated to SwiftUI. You don't need to rewrite your whole app to get the benefits. We're a hybrid system with direction. UIKit provides stability. It's still fantastic for deep UI customization like complex collection views or intricate navigation bars. SwiftUI defines our growth. It's catching up fast, and our plant identification feature seen here is 100% SwiftUI.
So everything I've described so far is specific to AllTrails. Our scale, our members, our constraints, and that's intentional. The common mistake teams make is treating adoption like a binary decision. Should we adopt SwiftUI? The better framing is, under what conditions does adoption create momentum instead of risk? When we looked at what actually worked for us, fewer bugs, faster delivery, better cross-team scaling, it wasn't a single technical choice. It was a set of decisions made deliberately.
One question I hear a lot is, can UIKit and SwiftUI coexist safely? And from what I've shown today, the answer is most certainly yes. So instead of telling you what to adopt, I'll leave you with three questions you can use to evaluate adoption in your own context. Thanks for watching! [Transcript missing] Two, does the new tool reduce cognitive overhead? When a tool genuinely simplifies mental load, adoption doesn't need enforcement. Engineers will choose it voluntarily.
And three, are you measuring momentum and not conversion? Momentum shows up in smaller PRs, faster reviews, and fewer regressions, not in how much of your code base you've converted. So if there's a takeaway here, it's that you don't need a rewrite to make progress. You need direction. So thanks so much for your time today and for letting me share the AllTrails story. We'll see you on the trail. applause Thank you so much, James. It's inspiring to hear firsthand how SwiftUI makes it easier to ship features and reduces the maintenance. And that SwiftUI can exist so smoothly alongside a UIKit code base. I'm a big fan of AllTrails, and it's fun for me to learn more about how it's made behind the scenes. All right, we're going to take one more break and then meet back here at 225 Pacific for a panel with leaders in SwiftUI engineering. I'll see you then.
Panel with SwiftUI Engineering
I hope you enjoyed the break. And now it's time for our panel with three leaders in SwiftUI engineering. So please join me in welcoming Nick, Russell, and Taylor to the stage. Nice. Hello, Leah. So, I've had the pleasure of working with you all before, but for our audience, please introduce yourselves and tell us a little bit more. How long have you been at Apple? Well, my name is Nick Teisler. I've been at Apple for about six years. I initially joined the AppKit team, and that was already a dream come true because I had just learned about how AppKit engineers had kind of pioneered object-oriented programming, And there really is still the opportunity to work with people who were at Next and Apple back in those days. And so that's how I got brought in. Very cool.
I'm Russell. I'm a Swift UI manager now, but I've been at Apple for nine years. And I primarily was an engineer on UIKit for most of my career here until very recently, but came here straight from college. And I'm Taylor. I guess I get to be the oldest here by being 13 years. I also started on AppKit similar to Nick way back when, moved into SwiftUI and most recently actually come full circle bringing back some AppKit and Catalyst responsibilities. So really excited to be here. - I feel like you're all telling the interoperability story between AppKit, UIKit and SwiftUI just by virtue of your history and experience.
So talk a little bit more about what motivated you to transition and work on SwiftUI and what you love about the framework. Well, after working with the AppKit people who are pioneering object-oriented design, I saw Taylor off on another team pioneering declarative framework design. And so I really thought that was the next place to go. Yeah, I mean, to add on to that, I think the declarative nature really is what kind of drew me in as well.
And especially how it was powered by Swift. You could write a declarative system. And maybe to back up, when we say declarative, we mean you're telling a framework of what you want to have happen, rather than all the steps to get there, which is the imperative way of building things.
So this was a really powerful idea that you could build a declarative system in any language, including Objective-C, but Swift made it feel even more natural and expressive to write this. So it's a unique opportunity to really explore these ideas, and that is what drew me in as well. Oh, sorry. I didn't want to cut you off. No. I also just see it as trying to solve the same problem I've always been interested in solving, which is making it easier to make apps. And the UI frameworks are not even so different from each other. And it's not even like SwiftUI doesn't have imperative aspects to it. Like if you drop down into the layout protocol or Canvas or Path, like there's, it's just having a declarative API is another layer of abstraction that's useful for solving the kinds of problems we've always been trying to solve. Russell has a dream he always talks about of like a bug-free future, We're happy to do what you want them to do and everything is right in the world. Yeah, I want to live in that reality.
Do you have a way in? Can you hook me up? I think it's this. Yeah, I mean, we've got the logo and everything. Like, it's big. Well, I loved how you described the simplicity that SwiftUI strives for and, in my opinion, achieves from the first moment that I used the framework. The declarative interface makes it not just graceful but fun to learn and invest time in. So SwiftUI was introduced in 2019. Can you share more about the problem it intended to solve, and then also how that problem statement and approach has evolved over time?
I'll jump in. Yeah, Taylor's I'll jump in first. You've got the seniority, right? Yeah. The most years. I mean, I think the problem I was trying to solve was kind of making it, lowering the barrier to building apps, right? And taking advantage of things like the declarative system. We've described how we really want a single source of truth so that you don't end up with the types of bugs you might otherwise end up with in other types of apps. So it was really these goals were at the heart of a lot of what we were doing. And so the first few years was really setting a foundation of these principles to build from.
And then every year after that is about adding additional capabilities. There's a very long history of capabilities that UIKit and AppKit provide. And our goal is to match those. We're not even there yet to this day, but I think we're reaching a point where I'm impressed with the type of apps people can build.
To add on to what Taylor said, I think one thing that's been consistent from the beginning is we write the APIs so that we've got the tagline, learn once, kind of write anywhere. But there's also this idea that once you're familiar with parts of the framework, you become familiar, you're automatically familiar with a lot of other parts of the API. So you can imagine back to UIKit where you've got UI table view and you learn all about the UI table view API surface, and then you move to another component and you're like, oh, this has kind of other different patterns I need to become familiar with. But with SwiftUI, the idea is everything kind of feels familiar from the start.
So that's been a steady thing. Like a driving force. Yeah. And I think that extends not just to actually using the APIs, but also taking one piece of code and bringing it to different platforms and different hardware, and able to see your apps presence in different contexts and build your skill set.
I think one thing that Russell was calling out in another discussion was, as we reach towards the end of having the full feature set that apps expect, is going to a next layer. And I think we're shifting towards that movement of, what are the really unique things that you can build with SwiftUI and the types of tools? Yeah, cuz I think when we started, we got a kernel of a good thing out there, once the idea was really solid. But then there were many years of just getting parity with the other frameworks. And I think now as we're approaching that, we can shift priority back towards even some of the core, like what are new ideas that we can bring into the framework and evolve the core framework itself. - Yeah, absolutely. Something that comes to mind for that for me is the scroll view APIs that were introduced and how, and you could speak to them better than I, but kind of taking it to that next level of making it easier for the developer to implement really sophisticated views without that much code. And yeah, go for it. Very glad you touched on that.
Quick question for the audience. How much of you in your apps are using SwiftUI and you've got a geometry reader inside your scroll view? There it is. Put them up. I'm doing some math. Nice. OK. I'm seeing some hands. You probably don't need that anymore. There are other APIs that have existed for two years and longer that are designed explicitly to make this more ergonomic and possible. And so-- You're talking about the scroll position modifiers? Scroll position modifiers, they're really neat ways to tie your animations and drive your animations by scroll view. And there's sessions for that as well in the past. You might have to go back to 2023. But anyway. I'm going to throw it out. View align scrolling, page align. Page align scrolling. I think Kurt did it in his presentation.
Scroll target behavior, scroll target layout. Even the layout protocol itself was addressing some of the needs that the geometry reader required. So I think these are great examples that show the feedback loop as well. When we see how people are using things and running into trouble, it allows us to think about what's the next step to improve the framework as well. Which is I saw someone do something really cool where you can, I can't explain it exactly at the moment, but you use coordinate spaces and you can access the coordinate space of the scroll view through I think the scroll proxy, the scroll reader proxy and do a lot of interesting stuff with that without having to have that invalidation cycle that Geometry Reader can sometimes bring. I feel like we could go on for scroll views forever. There's a lot to talk about. But I want to go back to something you said in the impact of developers and seeing how people use the framework and wanting to make SwiftUI not just have parity with UIKit and AppKit, but actually go to that next level and make it easier to make delightful experiences. Could you talk about the impact that the developer community has on SwiftUI?
That was the original reason when I said, Taylor, I would like to join your team. He was like, what do you like? And I said, to me, the UI in SwiftUI doesn't stand for user interface. It stands for you and I. But it really is true. It's just like when you meet anyone and you have a shared hobby. You meet anyone and they're like, you're both runners or you both like a certain movie genre. I've met so many people in other countries as I'm traveling around who I've just reached out to because we both like UI frameworks.
And you immediately have this shared language. So it's honestly like a great way to kind of see the world and meet people. Yeah, I mean, I think that this like feedback loop that I described earlier of like every year we get to release what we think are the next most important things. See what people are building and how they respond and iterate from there. I mean, like I said kind of earlier, the original goal was lowering the barrier to entry to building apps.
And even though that was a goal, we were still kind of impressed and surprised that like once we released SwiftUI, there were people building apps for the first time that never did before. You know, designers was one group of people like this and many others. You know, the generative AI tools are another good example that have really empowered people to, you know, every day I'm seeing like, I built this new app that I really did something I wanted and it's the first time I've ever built an app. And it's so cool to see that and then see, you know, what are the things that those new new types of people building apps run into and how can we make that experience even better? - I mean, to even add to that, the whole reason I wake up in the morning is to add new things so that y'all can build wonderful apps. I mean, I came from the developer community myself before I worked here. It was like one of the things I did, it's how I learned how to code, was reading Apple's documentation for how to make iOS apps. I mean, making something move on the iPhone screen was a lot more magical than the terminal app I was playing with before. So continuing to add to that is maybe someday I'll graduate back to getting to make apps. Yeah. When we say like at the end of our videos, a lot of the times we'll always say like, we can't wait to see what you build. It's really true. One of the most delightful things is where you spend the whole year studying and building this API and you have your, in your head, you've envisioned how people are going to use it. And then you see someone like, here's what I did with this API. And you're like, oh, and then. That was like a good. At first you're like, I didn't expect it to go that way. And then they're actually doing something really cool. And that really means that we've met our goal, too, in terms of making a compositional API that behaves generally in ways you can do things unexpected.
Yeah, unexpected. That's awesome. No, the evolution of the framework, it's been a joy to see not just how sophisticated it's become, but how delightful. And I love events like this where we get to connect with developers both here and online, ask questions, and really see how these APIs are being used, and what they're doing great, and how they can continue to better serve the needs of your app. So I think it's super rewarding. And I'm going to speak for all of us. It's rewarding. It's really rewarding. So this is a question on Slido. A lot of people were asking about an endorsement for a recommended app architecture in SwiftUI. Could you share more about that? We get this one a lot. And I think the world's come a long way we could kind of try to recommend one architecture and say one size fits all and then we send people down this road of this one architecture and it turns out maybe your app is extremely server driven and so that architecture wasn't the right move for you so SwiftUI's goal is really to provide the building blocks that every architecture needs like we're well aware of all the architectures the community is building one thing I like to call out is that the ad observable macro is kind of the place to start when you're if you've got a view model based architecture like use that observable use that atomic building block and it's first class integration with swift ui to build your view models and also observable can then be used kind of in ui kit without loss of generality which is which is really nice too and reinforcing this is the whole like one of the key goals is interoperability and we saw this story of all trails of how they adopted the circuit incrementally and that's true for for so many apps and each of those apps are coming from a different place app architecture-wise and it is Truly so important that SwiftUI is able to be catered to wherever an app is coming from. Yeah, even within Apple, so many apps rely on that interoperability. Yeah, for sure.
It's really what lets your team, what clicks with all the individual people on your team and how they like to build fastest and what they think is most elegant. And there's so many resources. If your app is in the UI kit land or the app kit land today and you're thinking about when to step into the pool of SwiftUI adoption, There are so many resources on the developer website, WWDC videos to help guide you through that process. It's a really well-traveled path. And I think there's so many great resources to take that first step.
So another question from Slido, and this is more high level. A lot of people were asking about specific, what is the API for this behavior on a specific platform? Or how do I do this functionality in my views? And I'm curious, at a higher level, how do you recommend that people find the API for different behaviors with SwiftUI? What tools do you recommend?
I've been having a lot of success with the various model integrations into Xcode. At least that's a great place to start. You can always say, because you get to say in whatever language you're speaking. Actually, I haven't seen the models tested in various languages. But you get to say in natural language what you're looking for. And then you immediately get kind of a grab bag of modifiers and suggestions. And that is actually a really helpful place to start.
Yeah, the intelligence features are so cool. And they really reduce the barriers for prototyping. That's something I find just to get started moving in maybe an area that I'm not as well versed and then can further refine my understanding. - The latest releases are really cool in that regard. But I do think it's an important thing that, it's gonna produce something, it's gonna try to show you something. But it is your job as the developer to question that and make sure you understand what it's doing. We're all familiar with things like hallucinations and things like that. And so in addition to verifying that, I think it's also a powerful learning tool, which is I think how you started this, which is, okay, it suggested these set of modifiers to achieve this effect. Do I understand what each of those are actually doing? And that can lead you into the documentation. And one of our goals is making sure each piece of documentation describes it, has a little piece of sample code. So you can yourself start to build up a mental model, you know, driven by this type of learning, which I think is cool. - Yeah, and that's what we're here for today, the foundations. Like, I think I've said that word about 10 times today, maybe more, we'll count it. But like taking the time to understand the foundations helps you have the confidence in your code. And then if you don't understand something, maybe the LLM produces it, you can still take that time to find exposure to areas that you aren't as confident in yet and learn it. So it's really cool. - That's one thing, yeah. Especially when you're trying to learn, I'm almost never learning and writing the app at the same time. Like I'm always learning with Xcode previews. And then once you get the foundation, then you take that into the app where you have all that other side effects and complexity happening at the same time, but at least you have that model for how it's supposed to work. So when things seem off or you might have done something where you know kind of where to look and where to start. - Yeah, previews is one of my favorite tools. Not just the fact that it makes it easy to isolate state and reproduce bugs, maybe there's like some odd behavior and I can just quickly inject state and see how it's behaving, but quickly prototype. Like for layout, I love using previews for layout. It's one of my favorite tools. Another resource that just came to mind is the developer forums. So in addition to documentation and the WWDC videos, which is really not just how I learn about different APIs, but also just see them in the larger context of a sample app and see how the different pieces come to mind. Like that scroll view video was awesome because it came up with so many ideas for animations. But the developer forums are also a great resource because you can ask questions by the framework, like SwiftUI or UIKit, but you can also ask platform-specific questions like watchOS or visionOS. And even if you don't have a question in that moment, maybe you just want to learn and get some awareness to the kinds of things you could be thinking about in the future, it's an awesome resource. And Apple engineers and support team are moderating that. So it's a great reference as you're building and continuing your journey as a developer.
So I want to also talk a little bit about SwiftUI helping apps stay modern. This year with Liquid Glass, that was a big part of the iOS 26 story and other operating systems. Could you share more about how SwiftUI helps apps stay modern for Apple platforms? I mean, first I can just say that there's a, like SwiftUI provides a lot of higher level semantic APIs, higher level than even the UI kit and app kit provided in the past, which lets us provide a lot more flexibility so that when your app is implemented with these semantic concepts, things like even prior to Liquid Glass, dynamic type, dark mode, these adaptive concepts, when those things change, your app just updates. Liquid Glass has many wonderful components, but it's also a theme in many ways. It doesn't necessarily change the core structure of your app. implementing it with these semantic concepts lets it just update.
But there's also like it applies to custom controls as well. So even if you do make something custom, that might need some adaptation. But there's other APIs like Glass Effect Container that like SwiftUI in particular enabled beyond the way that the other frameworks have because of our extremely composable APIs, the API surface for GlassEvek container is very small.
I think there's the one view and then maybe three or four public modifiers. And yet it's like this, it has all these transitions and like pieces that can merge and morph together. And you could imagine like how an imperative UI framework might struggle to deliver an API that was as concise, understandable that used as many pieces that like already existed. So I think 50 has really helped us with Liquid Glass. - And I think this goes back to, Nick called out this phrase of learning once and applying anywhere, and it doesn't just apply across frameworks. But even within this concept, it's like all of these animation techniques you might have learned for some other purpose apply equally in this context. So I think it's another good example of that principle.
- Yeah, to me I think it's like knowing when to reach for the containers and knowing kind of those, again, the foundations, it keeps coming up, knowing those foundations to reach for, and then knowing exactly where to sprinkle in your branding or what makes your app special, and making sure that's kind of robust to the passage of time, 'cause operating systems evolve and you wanna make sure that you're not kind of, you're not stuck with a lot of work to change depending on how you architected your app. And so, if you kind of push those unique experiences in the right spot, then like you can take your, if you've got a very fancy animation when you tap the like button, something jumps up and spins and dances, and that's immediately across all platforms and very robust to change and very future proof.
- Yeah, something that struck me through the workshops with developers, the Liquid Glass workshops, is that if they had built with native components like the tab view and using system controls, that just recompiling with iOS 26 automatically set those apps, whether they were UIKit or SwiftUI, up in a great spot to start taking advantage of Liquid Glass, look modern on Apple platforms, and then get to start going to the next level of refinement and thinking of opportunities to maybe revisit navigation or revisit branding. And that was really cool. Like it felt kind of magical. And I think it's really great that that's part of SwiftUI's foundation as a framework, removing that barrier to stay modern and take advantage of the sophistication of the operating system. I think one measure we've looked at is like the time spent doing just the things to make your app look like it's part of the platform. we want to decrease this as much as possible so that you're spending all your time on what really makes your app unique. I think that's a great way of showing that. Yeah, absolutely. Could you talk a little bit more about system components? I think there's really a lot to tease out there. In Wishlist, the sample app today, Maho discussed it in her design presentation.
There's a lot of core system components like the tab view that we kept really clean using SF symbols for the icons and letting the app lean into the familiar standard system architecture, but then we use typography and different fonts to express personality and branding. Could you talk about how you recommend people navigate those trade-offs, when to use built-in controls versus express a little bit more personality?
That's a good one. Boy. - And there's a lot to go into, 'cause it depends. - There's a lot, yeah. - It really, I was gonna say, it really is a design decision of like, where do you want your app to be unique? Where do you want it to feel familiar? I think there is a benefit to the familiarity, 'cause like somebody who's downloading your app for the first time, you want them to be able to have, have it be familiar and not have to learn how to use your app uniquely.
And then there are the little experiences that kind of draw them in, right? that make it stand out. That's a great lens, I feel like, to look at it through. Russell did a whole lot of work. He built a lot of UI presentation controller in Sheet and Swift UI Sheet. And Sheet is just one of my favorite components for that, because people, to Taylor's point, they just know how it's going to work.
It's such a core piece of the operating system that I really hesitate to provide global guidance, but try not to build your own Sheet. Right? Yeah. There's a-- No, that's trustworthy. Not just for me. Not just for you. Okay, I'll take your word for it. Well, there's a theme over the last several questions that I think of progressive disclosure, where both from the user's perspective, like the more things you use that are in common with other apps, then the user can better understand how your app works. But also in the API, if you use a higher level semantic component, then it will both be more familiar to you and everyone that uses the app and adapt more. But it's also easier for you as the developer to understand because it's a simpler, higher level concept.
And if you drop down into building increasingly into customizing that thing more and more for your use case, that it becomes more complex for everyone, maybe appropriately so. But we wanna ensure there's a continuum. And so adding more like ways to appropriately customize the high level components such that you don't have to like fully drop down is like very important. And I think I was connecting this to whatever the last thing you were saying was. No, I felt it.
I felt it. I mean, something that came to mind for me is that there's also a lot you can do with the system components, like talking about sheets and some of the beautiful behavior with iOS 26, like how the background changes a little bit based on the height of a sheet. There's a lot of tools that exist to customize the behavior of a sheet and modifiers. So sometimes you can get, and it takes a little time to read the documentation and find out what options exist. But you can set the presentation detente. Is it detente or detente? I always struggle.
Sorry. That's my secret. I always struggle with it. I'm responsible for the creation of this API word. Detente. It's detente. Okay. I'm noting it down. It's detente. Oh, it's detente. See, I was Like a micropipette. Like a micropipette. But there's a lot you can do that's built into the API to get a really sophisticated behavior and customize these built-in navigation tools and these built-in controls to express personality in your app. Things like, yeah, view modifiers, fonts, like there's so much that you can do with the system components. There are still limits which we want you to hit and find and tell us and we're listening so that we can remove those limits and make it even more powerful.
One of my favorite things that sheet does, especially with liquid glass, is as it, you might have to correct me here, as it grows taller, it becomes more opaque. But the sheet itself is glass. And, like, depending on the height of the sheet, it kind of nestles right into the device bezel or is floating depending on how big it is. And it's, like, it's very performant. It's very nice.
There's a lot of behaviors we've added to sheets over the years. Are you the king of sheets? No. Well, there's many of us that work on sheets to make that component work. Yeah. No, it's beautiful. It's such a joy to see how thoughtfully a lot of these system components are reimagined with the 26 operating systems and with Liquid Glass. And it's also been fun for me to look at-- Kat mentioned earlier the joys of looking at an app that you think is neat and doing that exercise of saying, "This looks cool. I'm going to try to build this by myself in SwiftUI." It's kind of like that empty page problem. Maybe you want to build skills with layout or with data flow, and you don't really know where to start, and you're wondering, do I need a design? And that's kind of the first problem you solve. But doing that independent exercise to try to build something by yourself from scratch is, I think, a great way to gain experience and cement that learning. Yeah, so true. I did that the other day with the app on the home screen of iOS, the app wiggle animation. Like when you hold something down? - Yeah, okay. - They lock into edit mode and it kind of Wait, all three, I want to see who does the best like this. It's random, it's really hard to get right. I think it's Russell, yeah. Russell. It's not just a repetitive curve and so just implementing that on a square in SwiftUI is kind of a fun exercise. And I'll give you a hint, I'm pretty sure you're going to need the should merge property of custom animation. That's advanced. That's, I was thinking of phase animation. Okay, cool. Yeah, it's mentioned in dub dub video for animation. That's really cool. These might be a very valid way of doing it too. See, and there's multiple ways to solve a problem too. Like I think that's something that's really fun to do. Sometimes I'll try to redo something and maybe I'll start with one approach and it's kind of there and I'll keep going and I'm learning along the way in these incremental approaches. Yeah, I think it goes back to the whole progressive disclosure that Russell mentioned, which is like a core principle of the API design itself. But it's like even pushing yourself to the next layer. Like you try to do a thing, you've gotten pretty close, What's the next thing you can try to learn about and experiment with to make it even closer to what you wanted or bring a new unique element into it? - Yeah, I feel like no matter what level of experience someone has with SwiftUI, whether they're just starting out or they're experienced in trying to solidify their understanding, there's always more to learn and discover, both with just the new APIs and finding more interesting ways to implement things. Very cool. So I have one last question for all of you. We've been talking about wishlist a lot today and different trips and the stuff that we wanna do on these trips, lots of activities, lots of travel. What is your next vacation?
I'm very much hoping to see Scotland this year. I've never been. I want to see the countryside. I want to drive on the left-hand side of the road. That's a dream of yours? Some Scottish slang. You can? You can? What does that mean? I think that's like, you know. Okay, learn more. You got to test that out when you get there and see if it goes. If there's anyone in the audience who knows, come find me. That's a mixer. Very cool. I actually don't know. Shout out to all the Sagittarius out in the audience.
My birthday is over the holidays, and so my sister planned a deferred surprise trip later this month, and I don't even know where we're going, so we'll find out. That's an adventure. And in Wishlist, you could just put, like, question marks for all the activities. It could just be, like, a random number generator. You never know what's coming next. That could be a feature, yeah.
Yeah. So I'm one of those people who doesn't like to relax on my vacations, as ironic as that sounds. You're an active vacation person. I'm an active vacation person. And so we like to do different hikes. And so one of our upcoming ones is we want to do a multi-day hike through the Dolomites in Italy. That's one.
No plans yet. We want to figure out when we can do that. Nice. That sounds great. We've got like two international trips and a big mystery. Russell, maybe your trip's to Cupertino or somewhere new. I guess we'll find out. We'll have to follow up soon. Well, thank you all so much. Could you all join me in giving them a round of applause? I learned so much and it's so much fun to talk about CCI. So thank you. Thank you.
It is so much fun to learn more about SwiftUI, not just from the technical details, but the story behind the framework. Today was an action-packed day. We covered foundational concepts of SwiftUI from the visual layer with design and layout and motion all the way down to data flow. And at every step, we covered the theory behind the decisions in real apps like Wishlist. shared firsthand how SwiftUI helped AllTrails ship and maintain features more efficiently.
Throughout all these chapters, I hope one thing remains clear. Take the time to invest and learn the foundations of SwiftUI. It will help you work with the framework to make your apps great. My colleagues and I are so grateful that you took the time to learn with us today. On Slido, we answered over 200 questions. And to continue the conversation, check out the Apple Developer Forums, where you can ask questions about SwiftUI and more.
The code for the Wishlist app will be available for download in the coming weeks. It's a super fun app to play with, whether you're planning your next trip or revisiting some of the exercises from today's presentations. My colleagues and I mentioned certain videos and documentation during the presentations, and later today we'll send out an email with the links to the resources so you can keep learning.
One of the best ways to learn about SwiftUI and other technologies is through videos from WWDC. There are hundreds of videos, and you can watch them on the Apple Developer website or in the Developer app. You might even notice some familiar faces from today's presentations. It's been an action-packed day, but before we go, I have one more special guest. You might have seen her before in the keynote, or so too, as well as our recent Swift Student Challenge launch. She'll share a few words and help us close out the show. Please join me in welcoming Susan Prescott, VP of Apple Developer Relations. Hey! Hey!
I am so excited, first of all, about my job. I hope you all love your job too. But it is super fun. As you can see, hopefully throughout the day today, and there's many more people that weren't part of this today, there is a lot of passion and commitment to the developer community in this organization around the world. And I hope today was one of the days you could really feel it.
I want to say thank you for being here, which you've already heard from a bunch of people. I want to say thank you to all of the people who participated on stage, including James from AllTrails, gets a little gold star for being here and sharing his experiences as well. So I have to ask, was it a good day for you guys, unofficial survey? Like, how was it? Yeah.
Do we have to do like Robert rules of order and now I have to ask the people who want a boo to go or can we just skip that? I'm glad I could feel the energy in the room. I could feel the energy from the audience and I just, just love that. So as Leah will explain in more detail, we have an opportunity after this for those of you who are here in person. We love those of you who are participating online as well. And that matters a lot. So, hey, and we love you. You're going to have to get your own glass of juice because here we'll have a mixer for everybody. And, yes, there'll be some light refreshments.
But I think the most exciting part is that the people that you've seen on stage and many other folks who work at Apple in the engineering and developer relations teams are here to talk with you. and you all have opportunities to meet each other as well because the community isn't hub and smoke from Apple. It's all you guys together and then us being part of it where we can. So anyway, I'm going to hand it to Lisa, to Lisa, to Leah to close. I just want to say thanks and I'll see you guys out at the mixer. So thank you. Yeah. Thank you.
Thank you all so much for joining us today in Cupertino and online. You're the heart and soul of our developer community. I hope you learned something new and have ideas for what to explore next. For everyone joining in person, we'll meet outside in a moment for some refreshments and a chance to connect with Apple engineers and designers. To all of you, whether in person or online, we hope you'll join us again soon, either online or at an Apple Developer Center near you. Thank you. Thank you.