Streaming is available in most browsers,
and in the Developer app.
-
SwiftUI essentials
Join us on a tour of SwiftUI, Apple's declarative user interface framework. Learn essential concepts for building apps in SwiftUI, like views, state variables, and layout. Discover the breadth of APIs for building fully featured experiences and crafting unique custom components. Whether you're brand new to SwiftUI or an experienced developer, you'll learn how to take advantage of what SwiftUI has to offer when building great apps.
Chapters
- 0:00 - Introduction
- 1:34 - Fundamentals of views
- 13:06 - Bulit-in capability
- 17:36 - Across all platforms
- 20:30 - SDK interoperability
Resources
Related Videos
WWDC24
WWDC20
-
Download
Hi, my name is Taylor. Welcome to SwiftUI essentials! SwiftUI is Apple's declarative user interface framework, used to build apps across all of Apple's platforms. It's been adopted broadly within Apple as the basis for brand new apps and through incremental adoption within existing ones. And when you are building a new app or a new feature, SwiftUI is the right tool to use. There are a few reasons for this. SwiftUI comes with a wide range of features. These enable your apps to take advantage of the devices they run on, feel native on Apple's platforms, and add rich interactivity. And adding those features requires, less code. Enabling you to move from prototype to production faster, and empowering you to focus on what makes your app unique. SwiftUI embraces, incremental adoption, so you can take advantage of it, exactly where you need it.
There is no expectation that an entire app needs to be SwiftUI to be able to take advantage of it. These qualities makes it easy for anyone to learn how to build an app using SwiftUI. And understanding how SwiftUI enables these qualities can help you understand how to best take advantage of them. I'm going to start with explaining the very basics of how views work. Later on, I'll highlight some of the capabilities built-in to SwiftUI and how they work across Apple's platforms. And at the very end, I'll discuss SwiftUI's ability to integrate with other frameworks.
But before getting started, I have one more insight about SwiftUI and those working on it We love our pets, and we frequently debate which kind is best. I've decided to resolve this in the most objective way possible which pet can do the best tricks. And there is some tough competition.
Over the course of this video, I'll be using SwiftUI to build an app for tracking our pets, their tricks, and how they stack up. But I need to start somewhere and in SwiftUI, that's going to be with a View.
Views are the basic building blocks of user interfaces, and are important to everything you do in SwiftUI. Every pixel you see onscreen is in some way defined by a view. There are three qualities that make SwiftUI views special: they're declarative, compositional, and state-driven. Views are expressed declaratively.
You describe what view you want in your user interface, and SwiftUI produces the result. You can create text, Images using SF Symbols, and controls, like buttons.
This code creates a horizontal stack that consists of a label, which is a combination of an icon and title. A spacer and a text at the end. This declarative syntax applies to other containers such as scrollable lists.
This list is given a collection of pets and creates a horizontal stack for each one.
Now, every pet isn't called Whiskers, so I'll update the views in the stack to use the properties of each pet instead.
At no point did I need to describe the actions necessary to produce this UI, such as adding and removing rows to the list.
This is the difference between declarative and imperative programming. Those of you who have taught a pet how to do tricks are familiar with imperative commands.
Like with imperative programming, I can instruct Rufus here on each step of hitting a home run: Rufus, Come Rufus, Take Bat Rufus, Home Plate And so on, describing each step of the process.
In comparison, a declarative pet trick would be describing what you want to happen and letting a pre-prepared pup perform it for you. All you have to do is say what you want: seeing Rufus score a home run. And you can customize certain aspects of the trick, like bringing a custom made shirt for Rufus to wear.
Declarative and Imperative programming are not mutually exclusive. Declarative code enables you to focus on the expected result, instead of the steps to get there. And imperative code is great when making a change to state, or when there might not be an existing declarative component. And SwiftUI embraces both. A great example of this is Button. Buttons are added to the user interface declaratively, and part of its declaration is the action to perform when tapped. This action uses imperative code to make a change: in this case, adding a new pet to the list.
SwiftUI views are descriptions of what the current state of the UI should be they are not long lived object instances that receive imperative commands over time. This is why SwiftUI views are value types, defined using structs instead of classes.
SwiftUI takes these descriptions and creates an efficient data structure to represent them. It maintains this data structure behind the scenes. And it's used to produce different outputs, for example: what is shown on screen, the gestures and interactive aspects of the view And its accessibility representation. Because views are just declarative descriptions, breaking up one view into multiple doesn't hurt the performance of your app. You don't need to compromise organizing code the way you want, in order to get the best performance.
Composition is used throughout SwiftUI, and is an essential part of every user interface. The HStack I built earlier is a container view for the purposes of layout, and it places its children in a horizontal stack. Rearranging and experimenting with container views is really easy in SwiftUI. The code itself resembles the hierarchy of views that it creates.
A horizontal stack contains three views: an Image, Vertical Stack, and Spacer. The Vertical Stack itself contains two views of its own: the Label and Text This syntax uses a view builder closure, to declare the children of a container. In this example, I'm using HStack's initializer that has a ViewBuilder content parameter. This is a pattern used by all container views in SwiftUI.
Composition plays an important role in another SwiftUI pattern called View modifiers.
View modifiers apply modifications onto a base view and can change any aspect of that view. I'll start with Whisker's cute photo. First, clip it to a circle, add a shadow, and overlay a green border on top, that's her favorite color.
Syntactically, this looks very different than container views, but it results in a similar hierarchical structure. The hierarchy and order of effect is defined based on the exact order of the modifiers. Chaining modifiers together makes it clear how a result is produced and how to customize that result; all in an easy to read syntax.
View hierarchies can be encapsulated into custom views and view modifiers. A custom view conforms to the View protocol and has a body property to return the view it represents. The view returned from body, uses the same view building syntax I've shown so far enabling the same compositional features and quick iteration. You can create additional view properties to help keep your code organized as you'd like. I've refactored out the profile image construction into a private view property.
Through these kinds of incremental steps, I can continue to iterate and build a row view to be just how I want it.
Custom views can have inputs that change how their body is created. I've added a property for the pet that this row will represent, and I've used that property in the views returned from body.
With this change, I can reuse the same view to display information about Whiskers, as well as Roofus and Bubbles.
Custom views can be used like any other view. Here, I've used it in a List as the view to create for each pet.
List is a great illustration, of the power of view composition. This List initializer has a collection parameter. It's a convenience for creating a ForEach view. ForEach generates views for each element in a collection and provides those to its container. This view-based List initializer enables creating more advanced constructions such as multiple collections of data organized into sections. One for my pets, and one for everyone else's.
Lists can also be customized through the use of view modifiers. For example, adding swipe actions to each row.
Through composition of additional containers and modifiers I can incrementally build up an entire app.
The third characteristic of views in SwiftUI is that they are state-driven. When your views' state changes over time SwiftUI automatically keeps your UI up to date eliminating both boilerplate and update bugs. SwiftUI is maintaining a representation of the user interface behind the scenes. As data changes, new view values are created and given to SwiftUI. SwiftUI uses those values to determine how to update its outputs. My app now has the list of pets and their tricks. But the most important part of a pet competition is rewarding the ones that I think have the best tricks. This is Sheldon, they love getting strawberries for their award. I'm well prepared after having added swipe actions on each row.
When I tap on the Award button, its action is called. This modifies the associated pet object, and changes hasAward to now be true.
SwiftUI keeps track of any views that depend on this pet, such as the row view.
It has a reference to the pet and in its body reads whether the pet has an award or not establishing a dependency.
SwiftUI will call this view's body again with the updated pet.
It now returns a result that includes an Image to reflect Sheldon's award.
SwiftUI updates the outputs based on this result to display the new image on screen.
Any piece of data that a view uses in its body is a dependency of that view. In my app, I created an Observable pet class. SwiftUI creates dependencies to the specific properties used in view bodies. SwiftUI has several tools for state management. Two other important ones are State and Binding State creates a new internal source of data for a view. When you mark a view property as @State, SwiftUI manages its storage and provides it back for the view to read and write.
A Binding creates a two-way reference to the state of some other view.
I've written another view that makes use of these. This view allows me to rate my pet's tricks. It's using State to keep track of the current rating and change it over time. The value is displayed prominently in the middle, and it has two buttons to increment and decrement the value.
SwiftUI maintains the value of this state behind the scenes.
Similar to the earlier example, the button's action is called when tapped. This time, it increments the internal State of the view.
SwiftUI notices this change, and calls body on RatingView, which returns a new text value. The result is then updated on screen.
I'm going to focus in on views in the body, where the state changes are being made.
So far the changes are being made immediately, without an animation. Animations in SwiftUI build on top of the same data-driven updates that I've discussed so far.
When I wrap withAnimation around this state change, the resulting view updates are applied with a default animation.
SwiftUI applied a default cross-fade transition to the text. But I can also customize the transition.
In this case, using a numeric text content transition fits this perfectly.
With state and animation, I've built an encapsulated view component that has the interaction I want. Ultimately, I'm going to compose this view in the rest of my app.
Here I have another view, RatingContainerView, that combines a Gauge and a RatingView in its body.
Currently, these views each have their own state, which act as their own separate sources of truth for what the rating is. However, this means that when the rating view increments its own state, the container view's state and the Gauge do not change.
I've updated RatingView to take a Binding as an input, so that a two-way reference can be provided by its container view.
Now, the container view's state becomes the only source of truth, and it provides the value to the Gauge, and a Binding to the RatingView. Now they update in sync, and the animated state change applies to the Gauge as well.
SwiftUI provides many levels of built-in capability, giving you an even higher starting point for building your app.
I've just begun on the app for tracking pets and their tricks, I'm happy with where I've gotten. SwiftUI automatically provides adaptivity along several dimensions.
My app already looks good in dark mode. It supports several accessibility features, such as dynamic type. And it's ready to be localized. For example, I'm previewing it with a right to left pseudo-language to get a feel for how it might look in Hebrew or Arabic.
This is one of the things that are great about using Xcode Previews. It quickly shows you how your views look, including in different contexts. And it does this as you write the code, without needing to run the app over and over.
Previews are interactive even directly on device. You can understand exactly how a feature you're working on is going to feel, as you're building it.
One benefit to SwiftUI's declarative views is adaptivity. Views provided by SwiftUI often describe the purpose of their functionality as opposed to their exact visual construction.
Earlier, I showed how swipe actions are composed of views like Button. Buttons are a great example of an adaptive view. They have two fundamental properties: an action and the label that describes the action.
They can be used in many different contexts but still always carry that purpose of a labeled action.
They adapt across different styles, such as Borderless, Bordered or Prominent. And automatically adapt to different contexts, such as Swipe actions, Menus, and Forms. This pattern applies to all controls in SwiftUI, including Toggles.
Toggles have their own styles, such as switches, checkboxes, and toggle buttons. And in different contexts, appear as the idiomatic style to represent something that turns on and off.
Many views across SwiftUI have this same adaptive quality, taking advantage of composition to affect behavior and enable customization. This applies to some view modifiers as well. One of my favorite examples is searchable, which I'll apply to my list of pets.
When I add the searchable modifier, I'm describing that the view it's applied to is capable of being searched. SwiftUI takes care of all of the details, to make that happen in an idiomatic way. And through incremental adoption of other modifiers, you can customize the experience: such as adding suggestions, scopes, and tokens.
SwiftUI's declarative and adaptive views pack in a lot of functionality in just a few lines of code.
There are controls, like Button, Toggle, and Picker.
Container views like NavigationSplitView, or customizable multi-column tables.
Presentations, such as sheets and inspectors, and many more examples that you can explore in the documentation.
And when you're ready to create unique, custom experiences, SwiftUI also has another layer of API, that provides low level control.
You can build your own control styles; use Canvas for high performance, imperative drawing; create completely custom layouts; and even apply custom Metal shaders directly to SwiftUI views.
In my app, the scoreboard was a perfect place to create a unique experience using these low level tools, evoking a classic flip-board.
I used animations, graphics tricks, and a dash of Metal shaders.
Sheldon, missed the landing on this last trick, so I'm going to have to give him a 7. Better luck next time, buddy.
SwiftUI's capabilities go beyond Views as well. The entire app definition is built on the same principles that Views follow. An App is a declarative structure defined by scenes. WindowGroup is one kind of scene. It's created with a content view to show on screen.
Scenes can also be composed together.
On multi-windowed platforms, such as macOS, additional scenes provide different ways to interact with your app's capabilities.
This pattern also extends to building custom widgets. Widgets are shown on your home screen and desktop, and are composed out of views. I've reused some of the scoreboard views to display Sheldon's latest rating. SwiftUI's capabilities extend to any platform it's used on and enable you to take your investments in one platform and build native apps on others. SwiftUI is available when building an app, for any Apple platform.
It's also a multiplier on your efforts: once you have a user interface built using SwiftUI for one platform, you have an excellent start to bringing that UI to any platform.
The app I've build for iOS is a great example of that.
Adaptive views and scenes provide idiomatic look and feel on any Apple platform. On macOS, automatically supporting things like keyboard navigation or creating multiple windows.
The same use of search suggestions results in a standard drop down menu on macOS, and an overlay list on iOS.
Custom crafted views from low level APIs will produce the same result across platforms and are another great place to reuse the same views when needed.
My efforts to perfect the scoreboard animation look great on all platforms.
While SwiftUI enables code sharing in these ways, it is not Write Once and Run Everywhere. It's a set of tools you can learn once and use in any context or on any Apple platform.
SwiftUI has a common set of these high and low level components across platforms, but it also has specialized APIs for each platform.
Every platform is unique in how it is used and therefore in how it is designed.
The Human Interface Guidelines describes components, patterns, and their platform considerations.
NavigationSplitView automatically adapts to watchOS's design, of a source list that pushes a detail.
And I reused custom views, such as the scoreboard. But there is one change I want to make, specific to watchOS.
Instead of using touch or the keyboard, I'd expect to be able to use the digital crown to quickly select a rating.
Building on the same scoreboard view, I've added an additional modifier for watchOS: digitalCrownRotation Now, as I turn the digital crown, it flips through to the score I want.
When I review pets and their tricks on my Mac, I want to dig into past data and compare it across pets. I can take advantage of macOS's flexible windowing model in different scene types. Or use views that make the most of macOS's familiar control library, information density, and precise input.
And I can bring my app to visionOS, taking advantage of views from other platforms and adding in additional volumetric content.
SwiftUI helps you make your app great wherever it takes you. This too is incremental in nature. SwiftUI doesn't require you to support multiple platforms. But it offers you a head start for when you're ready. The last area is not a built-in capability of SwiftUI itself, but in SwiftUI's ability to interoperate with the features and capabilities of other frameworks. SwiftUI is a framework that comes with each platform's SDK.
There are many other frameworks also part of the SDK, and each bring their own exciting capabilities. Any app won't use all of these frameworks, but can pick and choose from the ones that provide the technology they need.
SwiftUI provides interoperability to all of this functionality, and in many cases is as easy as dropping in another view or property into your app.
UIKit and AppKit are imperative, object oriented user interface frameworks. They provide similar building blocks as SwiftUI, but use different patterns for creating and updating views. And they feature long-standing, rich capabilities that SwiftUI builds on.
A cornerstone feature of SwiftUI is seamless interoperability with them.
If there's a view or view controller from UIKit or AppKit, that you want to use in SwiftUI, you can create a view representable.
This is a special SwiftUI view protocol for creating and updating an associated UIKit or AppKit view, using imperative code.
The result is a View that can be used within SwiftUI's declarative view builders and can be used like any other view, such as using it in an HStack. The inverse is true as well. If you want to embed a SwiftUI view into a UIKit or AppKit view hierarchy, you can use classes such as Hosting View Controller. This is created with a root SwiftUI view, and can be added to your UIKit or AppKit view controller hierarchy.
Apple's own apps use these tools to adopt SwiftUI incrementally, whether it's to bring SwiftUI into an existing app, or when building a brand new SwiftUI app and incorporating Kit views. All of these are tools in your toolbox, to build great apps. There is no expectation that an app needs to be entirely SwiftUI in order to take advantage of it.
Every framework in the SDK brings its own unique capabilities. SwiftData enables you to add persistent models to your app quickly, and comes with APIs to connect and query those models from your SwiftUI views.
Swift Charts is a highly customizable charting framework, built on top of SwiftUI, that makes it easy to create gorgeous information visualizations.
All of these frameworks are available to use to help you build great apps.
SwiftUI is built with a foundation of declarative, compositional, and state-driven views. On top of that, it provides platform-idiomatic capabilities and integration with a wide SDK. All of these things help you focus on what makes your app unique, with less code Provide a wide range of components that results in idiomatic and engaging applications. And enable incremental adoption along every step of the way.
And now it's time for you to get started with SwiftUI. Launch Xcode and begin creating your first app, or begin incorporating SwiftUI into an existing app. Check out other excellent videos on SwiftUI. A great one to watch next is an Introduction to SwiftUI.
Follow along with the SwiftUI tutorials, which guide you through building different apps. And there are many more treats in the documentation.
As for the pet competition, it's going to take more than an app to answer which pet is best. For now, my conclusion is that they are all... good pets.
-
-
2:30 - Declarative views
Text("Whiskers") Image(systemName: "cat.fill") Button("Give Treat") { // Give Whiskers a treat }
-
2:43 - Declarative views: layout
HStack { Label("Whiskers", systemImage: "cat.fill") Spacer() Text("Tightrope walking") }
-
2:56 - Declarative views: list
struct ContentView: View { @State private var pets = Pet.samplePets var body: some View { List(pets) { pet in HStack { Label("Whiskers", systemImage: "cat.fill") Spacer() Text("Tightrope walking") } } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String init(_ name: String, kind: Kind, trick: String) { self.name = name self.kind = kind self.trick = trick } static let samplePets = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking"), Pet("Roofus", kind: .dog, trick: "Home runs"), Pet("Bubbles", kind: .fish, trick: "100m freestyle"), Pet("Mango", kind: .bird, trick: "Basketball dunk"), Pet("Ziggy", kind: .lizard, trick: "Parkour"), Pet("Sheldon", kind: .turtle, trick: "Kickflip"), Pet("Chirpy", kind: .bug, trick: "Canon in D") ] }
-
3:07 - Declarative views: list
struct ContentView: View { @State private var pets = Pet.samplePets var body: some View { List(pets) { pet in HStack { Label(pet.name, systemImage: pet.kind.systemImage) Spacer() Text(pet.trick) } } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String init(_ name: String, kind: Kind, trick: String) { self.name = name self.kind = kind self.trick = trick } static let samplePets = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking"), Pet("Roofus", kind: .dog, trick: "Home runs"), Pet("Bubbles", kind: .fish, trick: "100m freestyle"), Pet("Mango", kind: .bird, trick: "Basketball dunk"), Pet("Ziggy", kind: .lizard, trick: "Parkour"), Pet("Sheldon", kind: .turtle, trick: "Kickflip"), Pet("Chirpy", kind: .bug, trick: "Canon in D") ] }
-
4:24 - Declarative and imperative programming
struct ContentView: View { @State private var pets = Pet.samplePets var body: some View { Button("Add Pet") { pets.append(Pet("Toby", kind: .dog, trick: "WWDC Presenter")) } List(pets) { pet in HStack { Label(pet.name, systemImage: pet.kind.systemImage) Spacer() Text(pet.trick) } } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String init(_ name: String, kind: Kind, trick: String) { self.name = name self.kind = kind self.trick = trick } static let samplePets = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking"), Pet("Roofus", kind: .dog, trick: "Home runs"), Pet("Bubbles", kind: .fish, trick: "100m freestyle"), Pet("Mango", kind: .bird, trick: "Basketball dunk"), Pet("Ziggy", kind: .lizard, trick: "Parkour"), Pet("Sheldon", kind: .turtle, trick: "Kickflip"), Pet("Chirpy", kind: .bug, trick: "Canon in D") ] }
-
5:33 - Layout container
HStack { Label("Whiskers", systemImage: "cat.fill") Spacer() Text("Tightrope walking") }
-
5:41 - Container views
struct ContentView: View { var body: some View { HStack { Image(whiskers.profileImage) VStack(alignment: .leading) { Label("Whiskers", systemImage: "cat.fill") Text("Tightrope walking") } Spacer() } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
6:23 - View modifiers
struct ContentView: View { var body: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:05 - Custom views: Intro
struct PetRowView: View { var body: some View { // ... } }
-
7:14 - Custom views
struct PetRowView: View { var body: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:20 - Custom views: iteration
struct PetRowView: View { var body: some View { HStack { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle() .stroke(.green, lineWidth: 2) } Text("Whiskers") Spacer() } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:24 - Custom views: view properties
struct PetRowView: View { var body: some View { HStack { profileImage Text("Whiskers") Spacer() } } private var profileImage: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:34 - Custom views: complete row view
struct PetRowView: View { var body: some View { HStack { profileImage VStack(alignment: .leading) { Text("Whiskers") Text("Tightrope walking") .font(.subheadline) .foregroundStyle(.secondary) } Spacer() } } private var profileImage: some View { Image(whiskers.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(.green, lineWidth: 2) } } } let whiskers = Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers") struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String init(_ name: String, kind: Kind, trick: String, profileImage: String) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage } }
-
7:41 - Custom views: input properties
struct PetRowView: View { var pet: Pet var body: some View { HStack { profileImage VStack(alignment: .leading) { Text(pet.name) Text(pet.trick) .font(.subheadline) .foregroundStyle(.secondary) } Spacer() } } private var profileImage: some View { Image(pet.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(pet.favoriteColor, lineWidth: 2) } } } struct Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } let id = UUID() var name: String var kind: Kind var trick: String var profileImage: String var favoriteColor: Color init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage self.favoriteColor = favoriteColor } }
-
7:53 - Custom views: reuse
PetRowView(pet: model.pet(named: "Whiskers")) PetRowView(pet: model.pet(named: "Roofus")) PetRowView(pet: model.pet(named: "Bubbles"))
-
7:59 - List composition
struct ContentView: View { var model: PetStore var body: some View { List(model.allPets) { pet in PetRowView(pet: pet) } } } @Observable class PetStore { var allPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] }
-
8:14 - List composition: ForEach
struct ContentView: View { var model: PetStore var body: some View { List { ForEach(model.allPets) { pet in PetRowView(pet: pet) } } } } @Observable class PetStore { var allPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] }
-
8:27 - List composition: sections
struct ContentView: View { var model: PetStore var body: some View { List { Section("My Pets") { ForEach(model.myPets) { pet in PetRowView(pet: pet) } } Section("Other Pets") { ForEach(model.otherPets) { pet in PetRowView(pet: pet) } } } } } @Observable class PetStore { var myPets: [Pet] = [ Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), ] var otherPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] }
-
8:36 - List composition: section actions
PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Award", systemImage: "trophy") { // Give pet award } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) }
-
9:31 - View updates
struct ContentView: View { var model: PetStore var body: some View { List { Section("My Pets") { ForEach(model.myPets) { pet in row(pet: pet) } } Section("Other Pets") { ForEach(model.otherPets) { pet in row(pet: pet) } } } } private func row(pet: Pet) -> some View { PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Award", systemImage: "trophy") { pet.giveAward() } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) } } } struct PetRowView: View { var pet: Pet var body: some View { HStack { profileImage VStack(alignment: .leading) { HStack(alignment: .firstTextBaseline) { Text(pet.name) if pet.hasAward { Image(systemName: "trophy.fill") .foregroundStyle(.orange) } } Text(pet.trick) .font(.subheadline) .foregroundStyle(.secondary) } Spacer() } } private var profileImage: some View { Image(pet.profileImage) .clipShape(.circle) .shadow(radius: 3) .overlay { Circle().stroke(pet.favoriteColor, lineWidth: 2) } } } @Observable class PetStore { var myPets: [Pet] = [ Pet("Roofus", kind: .dog, trick: "Home runs", profileImage: "Roofus", favoriteColor: .blue), Pet("Sheldon", kind: .turtle, trick: "Kickflip", profileImage: "Sheldon", favoriteColor: .brown), ] var otherPets: [Pet] = [ Pet("Whiskers", kind: .cat, trick: "Tightrope walking", profileImage: "Whiskers", favoriteColor: .green), Pet("Bubbles", kind: .fish, trick: "100m freestyle", profileImage: "Bubbles", favoriteColor: .orange), Pet("Mango", kind: .bird, trick: "Basketball dunk", profileImage: "Mango", favoriteColor: .green), Pet("Ziggy", kind: .lizard, trick: "Parkour", profileImage: "Ziggy", favoriteColor: .purple), Pet("Chirpy", kind: .bug, trick: "Canon in D", profileImage: "Chirpy", favoriteColor: .orange) ] } @Observable class Pet: Identifiable { enum Kind { case cat case dog case fish case bird case lizard case turtle case rabbit case bug var systemImage: String { switch self { case .cat: return "cat.fill" case .dog: return "dog.fill" case .fish: return "fish.fill" case .bird: return "bird.fill" case .lizard: return "lizard.fill" case .turtle: return "tortoise.fill" case .rabbit: return "rabbit.fill" case .bug: return "ant.fill" } } } var name: String var kind: Kind var trick: String var profileImage: String var favoriteColor: Color var hasAward: Bool = false init(_ name: String, kind: Kind, trick: String, profileImage: String, favoriteColor: Color) { self.name = name self.kind = kind self.trick = trick self.profileImage = profileImage self.favoriteColor = favoriteColor } func giveAward() { hasAward = true } } extension Pet: Transferable { static var transferRepresentation: some TransferRepresentation { ProxyRepresentation { $0.name } } }
-
10:57 - State changes
struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { rating -= 1 } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { rating += 1 } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
11:51 - State changes: animation
struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
12:05 - State changes: text content transition
struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .contentTransition(.numericText(value: Double(rating))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
12:22 - State changes: multiple state
struct RatingContainerView: View { @State private var rating: Int = 5 var body: some View { Gauge(value: Double(rating), in: 0...10) { Text("Rating") } RatingView() } } struct RatingView: View { @State var rating: Int = 5 var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .contentTransition(.numericText(value: Double(rating))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
12:45 - State changes: state and binding
struct RatingContainerView: View { @State private var rating: Int = 5 var body: some View { Gauge(value: Double(rating), in: 0...10) { Text("Rating") } RatingView(rating: $rating) } } struct RatingView: View { @Binding var rating: Int var body: some View { HStack { Button("Decrease", systemImage: "minus.circle") { withAnimation { rating -= 1 } } .disabled(rating == 0) .labelStyle(.iconOnly) Text(rating, format: .number.precision(.integerLength(2))) .contentTransition(.numericText(value: Double(rating))) .font(.title.bold()) Button("Increase", systemImage: "plus.circle") { withAnimation { rating += 1 } } .disabled(rating == 10) .labelStyle(.iconOnly) } } }
-
14:16 - Adaptive buttons
Button("Reward", systemImage: "trophy") { // Give pet award } // .buttonStyle(.borderless) // .buttonStyle(.bordered) // .buttonStyle(.borderedProminent)
-
14:53 - Adaptive toggles
Toggle("Nocturnal Mode", systemImage: "moon", isOn: $pet.isNocturnal) // .toggleStyle(.switch) // .toggleStyle(.checkbox) // .toggleStyle(.button)
-
15:19 - Searchable
struct PetListView: View { @Bindable var viewModel: PetStoreViewModel var body: some View { List { Section("My Pets") { ForEach(viewModel.myPets) { pet in row(pet: pet) } } Section("Other Pets") { ForEach(viewModel.otherPets) { pet in row(pet: pet) } } } .searchable(text: $viewModel.searchText) } private func row(pet: Pet) -> some View { PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Reward", systemImage: "trophy") { pet.giveAward() } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) } } } @Observable class PetStoreViewModel { var petStore: PetStore var searchText: String = "" init(petStore: PetStore) { self.petStore = petStore } var myPets: [Pet] { // For illustration purposes only. The filtered pets should be cached. petStore.myPets.filter { searchText.isEmpty || $0.name.contains(searchText) } } var otherPets: [Pet] { // For illustration purposes only. The filtered pets should be cached. petStore.otherPets.filter { searchText.isEmpty || $0.name.contains(searchText) } } }
-
15:20 - Searchable: customization
struct PetListView: View { @Bindable var viewModel: PetStoreViewModel var body: some View { List { Section("My Pets") { ForEach(viewModel.myPets) { pet in row(pet: pet) } } Section("Other Pets") { ForEach(viewModel.otherPets) { pet in row(pet: pet) } } } .searchable(text: $viewModel.searchText, editableTokens: $viewModel.searchTokens) { $token in Label(token.kind.name, systemImage: token.kind.systemImage) } .searchScopes($viewModel.searchScope) { Text("All Pets").tag(PetStoreViewModel.SearchScope.allPets) Text("My Pets").tag(PetStoreViewModel.SearchScope.myPets) Text("Other Pets").tag(PetStoreViewModel.SearchScope.otherPets) } .searchSuggestions { PetSearchSuggestions(viewModel: viewModel) } } private func row(pet: Pet) -> some View { PetRowView(pet: pet) .swipeActions(edge: .leading) { Button("Reward", systemImage: "trophy") { pet.giveAward() } .tint(.orange) ShareLink(item: pet, preview: SharePreview("Pet", image: Image(pet.name))) } } }
-
16:58 - App definition
@main struct SwiftUIEssentialsApp: App { var body: some Scene { WindowGroup { ContentView() } } }
-
17:15 - App definition: multiple scenes
@main struct SwiftUIEssentialsApp: App { var body: some Scene { WindowGroup { ContentView() } WindowGroup("Training History", id: "history", for: TrainingHistory.ID.self) { $id in TrainingHistoryView(historyID: id) } WindowGroup("Pet Detail", id: "detail", for: Pet.ID.self) { $id in PetDetailView(petID: id) } } }
-
17:23 - Widgets
struct ScoreboardWidget: Widget { var body: some WidgetConfiguration { // ... } } struct ScoreboardWidgetView: View { var petTrick: PetTrick var body: some View { ScoreCard(rating: petTrick.rating) .overlay(alignment: .bottom) { Text(petTrick.pet.name) .padding() } .widgetURL(petTrick.pet.url) } }
-
19:37 - Digital Crown rotation
ScoreCardStack(rating: $rating) .focusable() #if os(watchOS) .digitalCrownRotation($rating, from: 0, through: 10) #endif
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.