Streaming is available in most browsers,
and in the Developer app.
-
What’s new in SwiftUI
Learn how you can use SwiftUI to build great apps for all Apple platforms. Explore the latest updates to SwiftUI and discover new scene types for visionOS. Simplify your data models with the latest data flow options and learn about the Inspector view. We'll also take you through enhanced animation APIs, powerful ScrollView improvements, and a host of refinements to help you make tidier tables, improve focus and keyboard input, and so much more.
Chapters
- 1:05 - SwiftUI in more places
- 10:21 - Simplified data flow
- 18:46 - Extraordinary animations
- 27:18 - Enhanced interactions
Resources
Related Videos
WWDC23
- Animate symbols in your app
- Animate with springs
- Beyond scroll views
- Build accessible apps with SwiftUI and UIKit
- Build an app with SwiftData
- Build programmatic UI with Xcode Previews
- Design and build apps for watchOS 10
- Discover Observation in SwiftUI
- Explore pie charts and interactivity in Swift Charts
- Explore SwiftUI animation
- Inspectors in SwiftUI: Discover the details
- Meet MapKit for SwiftUI
- Meet StoreKit for SwiftUI
- Meet SwiftData
- Meet SwiftUI for spatial computing
- The SwiftUI cookbook for focus
- What’s new in Swift
- Wind your way through advanced animations in SwiftUI
-
Download
♪ Mellow instrumental hip-hop ♪ ♪ Curt Clifton: Hello and thanks for joining us. I'm Curt and I'm an engineer on the SwiftUI team. Jeff Robertson: And I'm Jeff. I'm also an engineer on the SwiftUI team. We're happy to have the chance to share what's new in SwiftUI. Curt: You can now use SwiftUI in even more places, including a brand-new platform! New data flow types dramatically simplify modeling your domain, providing more power than ever before. The Inspector, plus Table improvements, provide great ways to display your data. The team amped up our animation APIs, letting you create even more beautiful experiences for the people using your apps. Across the framework, we've enhanced your ability to provide great interactions, with powerful scroll view improvements, refinements to Focus and keyboard input, and deeper customization of controls like buttons and menus. I'm excited to tell you about some great new places to use SwiftUI. From the headset and watchOS 10 to new widgets and cross-framework integration, SwiftUI can help you create experiences that delight the people who use your apps. Spatial computing brings SwiftUI into a bold new future with all-new 3D capabilities like volumes; rich experiences with immersive spaces; new 3D gestures, effects, and layout; and deep integration with RealityKit. From core pieces like the Home View in Control Center to familiar apps like TV, Safari, and Freeform; to all-new environments like immersive rehearsals in Keynote; SwiftUI is at the heart of these user experiences. On this new platform, construct windows using familiar scene types like WindowGroup. WindowGroup scenes render as 2D windows, with delightful depth-sensitive 3D controls. Within a window, choose one of the usual SwiftUI containers, like NavigationSplitView or TabView. Within these containers, you can use all the usual SwiftUI controls just like on the other platforms. For even more depth, apply the volumetric style to your scene. Volumes display 3D experiences -- like board games or architectural models -- in a bounded space. They display alongside other apps. People can use your content while they jot down thoughts in Notes or update slides in Keynote. Fill a volume with a static model using Model3D. For dynamic, interactive models with lighting effects and more, use the new RealityView. To go truly all in, add ImmersiveSpaces to your app. The new ImmersiveSpace scene type lets you define immersive, spatial experiences, whether embedded in your surroundings or with full immersion. The system whisks other apps away letting people dive into the world you've created. Use an ImmersiveSpace with the mixed immersion style to connect your app to the real world, combining your content with people's surroundings. Anchor elements of your app to tables and surfaces, and augment and enrich the real world with virtual objects and effects. Go further with the full immersion style. Your app takes complete control. Build these connected and immersive experiences using the same Model3D and RealityView that work in volumes. SwiftUI on this new platform lets you create magical experiences. Watch "Meet SwiftUI for spatial computing" to continue exploring this great combination. SwiftUI is at home building a room-filling experience, but it can also build experiences for Apple's most portable displays. watchOS 10 delivers a redesigned user experience that surfaces timely information, conveys focused content at a glance, and celebrates the shape and fidelity of the display. We've updated apps across the platform to take advantage of this beautiful full-screen color and imagery. At the root of these designs are several existing SwiftUI views, newly empowered for watchOS 10. NavigationSplitView and NavigationStack get beautiful new transitions. TabView gets a new vertical paging style driven by the Digital Crown. SwiftUI introduces some new API to help you bring this full-color flair to your Apple Watch apps too. The new containerBackground modifier lets you configure these subtle background washes that animate when you push and pop content. You can also configure backgrounds for tab views on watchOS. And new multiplatform toolbar placements -- topBarLeading and topBarTrailing, along with the existing bottomBar -- let you perfectly place these small detail views in your Apple Watch apps. Besides these new additions, we're pleased to bring some existing API to watchOS for the first time, including DatePicker and selection in Lists. Now is a great time to polish your Apple Watch apps with these new capabilities. And if you don't have an Apple Watch app yet, it's a great time to begin. Learn how design and engineering come together to create these experiences in "Design and build apps for watchOS 10." Then apply these ideas to your work with "Update your app for watchOS 10." Widgets for the Smart Stack on watchOS 10 let the people using your app see their information on the go. SwiftUI is core to widgets wherever they appear, like these other new places. Widgets on the Lock Screen on iPadOS 17 are a great complement to widgets on the Home Screen. Big, bold widgets shine on the iPhone Always-On display with Standby Mode. And desktop widgets on macOS Sonoma keep people up to date during their day-to-day. Widgets have found their way to new places, and the team has also taught them another trick. I'm thrilled to share that widgets now support interactive controls. Toggle and Button in Widgets can now activate code defined in your own app bundle using App Intents. And you can animate your widgets using SwiftUI transition and animation modifiers. To get started with these great new capabilities, check out "Bring widgets to new places" and "Bring widgets to life." To develop and refine your new interactive animated widgets, you'll love the power of Xcode Previews. Previews leverage macros in Swift 5.9 to provide an elegant new syntax. Declare and configure a Preview, add a widget type, and define a timeline for testing. Xcode Previews shows the current widget state and a timeline that lets you see the animations between states. Of course, the new previews work with regular SwiftUI views and apps as well. And you can now interact with previews of Mac apps right inside Xcode. Check out the talk "Build programmatic UI with Xcode Previews" to learn how to leverage these terrific new tools to accelerate your app and widget development. Besides the macros that power previews, Swift 5.9 also brings a host of other improvements. Get an overview of all that's fresh in Swift by queueing up "What's new in Swift." Another way SwiftUI comes to new places is through SwiftUI-specific extensions to other Apple frameworks. Several frameworks bring new or improved support, and I'd like to highlight a few that I think are particularly exciting. MapKit delivers a massive update that gives you the power of Apple's marvelous mapping framework right in your SwiftUI code. Simply import SwiftUI and MapKit to use these great features. Put a map in your view. Add custom markers, polylines, and the user's location. Configure the available controls. To learn all about adding amazing maps to your SwiftUI app, check out the talk "Meet MapKit for SwiftUI." In its sophomore season, Swift Charts brings a slew of great improvements, including scrolling charts, built-in support for selection, and something I know folks have been hungry for: donut and pie charts with the new SectorMark. To dig into these new features, check out the talk "Explore pie charts and interactivity in Swift Charts." For building experiences that attract and retain loyal customers, you'll love the ease and power of the new in-app purchase and subscription stores. Present a subscription store view with your custom marketing content. Configure a full-bleed background to match your branding and choose from a variety of control options. Watch "Meet StoreKit for SwiftUI" to power up your in-app marketing game. From new platforms and widgets, to cross-framework integration and the beauty of watchOS, SwiftUI continues to push the Apple developer experience forward. It's exciting to see all the new places to use SwiftUI, Jeff. Jeff: Certainly! And we also have a lot of great improvements that work across all our platforms. Curt: Indeed! Jeff: We should work on an app that uses these improvements. Curt: Oh, agreed! Have you thought any more about my idea? Jeff: The one about dogs? Curt: Yeah! It's like bird-watching but for dogs! Jeff: Do you really think people want a dog-watching app? Curt: Oh, certainly! The pitch deck practically writes itself. Jeff: With our million-dollar idea in place, it's time to start building out our app. Since every great app begins with a great data model, let's start by looking at the pack of great new features SwiftUI has for working with our app's data. One of my favorite things about SwiftUI is how it lets me define my UI as a function of my app's state. I'm happy to share the biggest upgrade to how you define your model types with SwiftUI: the new Observable macro. Observable models let you use familiar SwiftUI patterns for data flow, while also making your code more concise and performant. Here's the model class I've set up to store data representing dogs I've met while out and about. To make this type an Observable, I'll add the macro to my type. That's all I need to do. Unlike ObservableObject, there's no need to mark properties as Published. Observable models easily integrate into the existing SwiftUI mechanisms for data flow. Let's use my DogCard view as an example. When using an Observable in your View, SwiftUI will automatically establish dependencies on properties you read. There's also no need to use a property wrapper when reading, so your view code is cleaner. And this view is reading the isFavorite property, so when that changes, it will get reevaluated. Invalidation only happens for properties which are read, so you can pass your model through intermediate views without triggering any unnecessary updates. SwiftUI includes several tools for defining your state and its relationship to your views, several of which are designed for use with ObservableObject. When using Observable, this is becomes even simpler since it's designed to work directly with the State and Environment dynamic properties. In addition to modeling read-only values, Observables are also a natural fit to represent mutable state, like on this form for a new dog sighting. The model is defined using the State dynamic property, and I'm passing bindings to its properties to the form elements responsible for editing that property. Lastly, Observable types integrate seamlessly into the environment. Since views throughout our app want a way to fetch the current user, I've added it to the environment of my root view. The user profile view then reads the value using the Environment dynamic property. I'm using the type as the environment key here, but custom keys are also supported. Be sure to catch "Discover Observation with SwiftUI" to learn more on how you can take advantage of this powerful new tool. I love how Observable lets me write clear and concise code. It's given me a great start to my app, but I'd really like to make sure any changes to my data model are persisted, so that I never lose track of all my favorite pups. SwiftData is an all-new framework for data modeling and management. It's fast and scalable and works great with SwiftUI. SwiftData models are represented entirely by their code, making them a natural fit for any SwiftUI app. To set up my Dog model type for SwiftData, I'll switch from using Observable to the Model macro. This is the only change I need to make. In addition to the persistence provided by SwiftData, models also receive all the benefits of using Observable. It's really powerful. Our dog-watching app's main screen shows a scrolling stack of recently met doggos. Let's take a walk through the changes needed to use SwiftData here. First, I'll add a model container to my app's definition and provide it my model type. Then, in my view code, I'll switch my array of dogs to use the new Query dynamic property. Using Query will tell SwiftData to fetch the model values from the underlying database. When the data changes, like when I spot a new dog, my view will be invalidated. Query is incredibly efficient for large data sets and allows for customization in how the data is returned, such as changing the sort order to use the date I spotted the dog, which makes for a better experience in the app. SwiftData also works great for storing a document's data on macOS and iOS. I decided I wanted a quick way to prototype some dog tag visual treatments for our app, so I built this little document-based app for collaborating with Curt and our designers. Document-based apps can take advantage of all the functionality of SwiftData using a new initializer. SwiftUI will then use SwiftData for the underlying storage of each document, as well as automatically set up a model container. To learn more about SwiftData and how it integrates with SwiftUI, please watch "Meet SwiftData" and "Build an app with SwiftData." In addition to SwiftData support, DocumentGroup also gains a number of new platform affordances when running on iOS 17 or iPadOS 17, such as automatic sharing and document renaming support, as well as undo controls in the toolbar. Inspector is a new modifier for displaying details about the current selection or context. It's presented as a distinct section in your interface. On macOS, Inspector presents as a trailing sidebar. as well as on iPadOS in a regular size class. In compact size classes, it will present itself as a sheet. To uncover all the details on Inspector, watch "Inspectors in SwiftUI: discover the details." Dialogs have been given several new customization APIs in iOS 17 and macOS Sonoma. I'm using some of the new modifiers to give my image export dialog some useful information, like adjusting the confirmation button's label. An increased severity helps draw attention to important confirmation dialogs, and including a suppression toggle indicates a preference that the dialog should not present itself for subsequent interactions. Lastly, adding a HelpLink can be a guide to further information about the purpose of the dialog. Lists and tables are a key part of most apps, and SwiftUI has brought some new features and APIs for fine-tuning them in iOS 17 and macOS Sonoma. Tables support customization of their column ordering and visibility. When coupled with the SceneStorage dynamic property, these preferences can be persisted across runs of your app. You provide the table with a value representing the customization state and give each column a unique stable identifier. Tables now also have all the power of OutlineGroup built in. This is great for large data sets that lend themselves to a hierarchical structure, like this one which groups some of my favorite dogs with their proud parents. Simply use the new DisclosureTableRow to represent rows that contain other rows, and build the rest of your table as you would normally. Sections within a list or table have gained support for programmatic expansion. I've used it here in my app's sidebar to show the location section as collapsed initially, but while still allowing for expansion. The new initializer takes a binding to a value which reflects the current expansion state of the section. For smaller data sets, tables have also gained a few new styling affordances, such as how row backgrounds and column headers are displayed. And lastly, custom controls like my star rating will also benefit from the new background prominence environment property. Using a less prominent foreground style when the background is prominent lets my custom control feel right at home in a list. In addition to these and other APIs that let you fine-tune the look and feel of lists and tables, we've also made big improvements to the performance, particularly when dealing with large data sets. To learn more about this and the ways you can optimize your own SwiftUI views, check out "Demystify SwiftUI performance." From Observable to SwiftData to Inspector and table customizations, working with data in your apps feels like a whole new experience. Curt: With the data model and tables that Jeff has put together, we have the bones of a great app. I'd like to add some pizazz using the extraordinary new animation APIs. I think it would be great to have an Apple TV app for viewing a gallery of dog photos. Here's an animation I've been working on for choosing the current viewer. I built this with the new Keyframe Animator API. Keyframe animators let me animate multiple properties in parallel. I give the animator a value containing animatable properties and a piece of equatable state. Changes to the state trigger my animation. In the first closure, I build a view, modified by my animatable properties, like the vertical offset of my logo. In the second closure, I define how these properties change over time. For example, the first track defines the animation of my verticalTranslation property. I pull my logo down 30 points over the first quarter second using a spring animation. Then I make my Beagle leap and land using a cubic curve. Finally, I bring this dog home with a natural spring animation. I define additional tracks for my other animated properties. All these tracks run in parallel to create this cool animation. To learn how to leverage keyframe animators in your apps, check out "Wind your way through advanced animations in SwiftUI." I've also been working on an Apple Watch app to record dog sightings when I'm out on a run. It's pretty simple so far, just our happy icon and a button to register a sighting. I'd like to animate this icon when I tap the button. This is a good place for a phase animator. A phase animator is simpler than a keyframe animator. Instead of parallel tracks, it steps through a single sequence of phases. This lets me start one animation when the previous animation finishes. I give the animator a sequence of phases and tell it to run my animation whenever my sightingCount changes. Then in this first closure, I set the rotation and scale of my happy dog based on the current phase. The second closure tells SwiftUI how to animate into each phase. I'm using some of the cool new spring animations here. I just love these names. Who doesn't want a snappy or bouncy animation? And for my grow phase, I'm using an entirely custom spring. Springs now take a duration and bounce, making them easier to describe. You can use all these new springs everywhere that takes a SwiftUI animation. Spring animations have a nice, natural feel. They match the velocity of any previous animation and settle to their final value with realistic friction. They're now the default animation for apps built on or after iOS 17 and aligned releases. I'm pleased with my animation now, but when I'm out on a run, it would be great to also get some haptic feedback. Haptic feedback provides a tactile response, such as a tap, to draw attention and reinforce actions and events. I think a tap on the wrist would give me more confidence that I didn't miss a dog. Haptic feedback is easy with the new sensory feedback API. To play haptic feedback, I just attach the sensoryFeedback modifier, specify what sort of feedback I want and when it should happen. The sensoryFeedback modifier works on all platforms that support haptic feedback. Different platforms support different kinds of feedback, so check out the Human Interface Guidelines to learn what sorts of feedback will be best in your apps. I've also been working on an animation for the welcome screen, using the new visual effects modifier. The visual effects modifier lets me update these dog photos based on their position. And I don't need a GeometryReader to do it. I've got a little simulation that moves a focal point around the screen. This red dot shows what I mean by focal point. I associate a coordinate space with this grid that shows all the dogs. Then inside my DogCircle view, I add a visual effect. The closure gets my content to modify and a geometry proxy. I'm passing the geometry proxy to a helper method to compute the scale. I can use the geometry proxy to get the size of my grid view and the frame of a single dog circle relative to my grid view. That lets me compute how far any dog is from the focal point of the simulation, so I can scale up the focused doggos. With visual effects, I can do all of this without using a GeometryReader. And it automatically adapts to different sizes.
I'd like to share one more example. I've been playing with a feature to send good-dog messages to the people of the dogs I've met. I thought it would be fun to style the dog's name to make it stand out. This is easy now that I can interpolate text with a foreground style right inside another text view. And check it out! I can adjust the styling using these sliders. Now how does that work? Here's how I'm defining the style. I'm passing my stripeSpacing and angle, along with a color from my asset catalog, to a custom Metal shader. Using SwiftUI's new ShaderLibrary, I can turn Metal shader functions directly into SwiftUI shape styles, like this one that renders the stripes in Furdinand's name.
If you'd like to take Metal shaders out for a spin, just add a new Metal file to your project and call your shader function using ShaderLibrary in SwiftUI. There's another thing in this example that I want to point out. Notice when I hit the end of the tracks on the sliders, the symbol gives this satisfying bounce. That effect is built in to Slider on macOS and iOS. You can also add it to your own symbols with the new symbol effect modifier. Just apply this modifier to animate an SF Symbol, or all the symbols in a view hierarchy. Symbols support a variety of effects, including continuous animations with pulse and variable color. State changes with scale, appear and disappear, and replace, and event notifications with bounce. Watch "Animate symbols in your app" to learn about the best people-pleasing practices for using symbol effects. Before leaving this example, I want to point out one last feature. Notice the units on the text here. In the past I might have used small caps for this effect, but now I can get this appearance by applying the new textScale modifier to my units. If Jeff and I bring our app to the Chinese market, the units will be sized correctly, even though the concept of small caps isn't part of the typography in Chinese. We have another tool to help apps work great in multiple locales. Some languages, like Thai, use taller letter forms. When text from one of these languages is embedded in text localized in a language with shorter letter forms, like English, the taller text can be crowded or clipped. When we know that this might be an issue -- for example, if our dog names were globally crowd-sourced -- we can apply the typesettingLanguage modifier. This lets SwiftUI know that the text might need more space. I'm having so much fun using these new APIs, but it's important to tastefully choose animations to avoid overwhelming people. To learn about the fundamentals of animation in SwiftUI, check out "Explore SwiftUI animation." Then in "Animate with springs," Jacob will help you build animations that feel at home on everyone's devices. The breadth of new animation APIs in SwiftUI is amazing. I've only scratched the surface here. There's even more to discover, from animation completion handlers to building entirely custom animations. I hope you enjoy these APIs as much as I do. Jeff: I love how all these new animations and effects really bring our app to life. Now, let's take a look at some of the new interaction APIs for the final touches. Interactions are at the heart of any great app experience, and these are just a few of the updated APIs coming to iOS 17 and aligned releases. My screen of recently met dogs could use a little extra flair to give it that final bit of polish. I'd like to add some visual effects to my dog cards as they transition in and out of the visible area of my scroll view. The scroll transition modifier is very similar to the visual effect modifier Curt used earlier for the welcome screen. It lets you apply effects to items in your scroll view. Using the scale and opacity effects gave me just that little bit of polish I wanted, with only a few extra lines of code. I'd also like to add a side-scrolling list of my favorite dog parks to this screen. SwiftUI has added some great features to let me build this. Above my vertical stack of dogs, I'll drop in a horizontal stack for the park cards. I'm using the new containerRelativeFrame modifier to size these park cards relative to the visible size of the horizontal scroll view. The count specifies how many chunks to divide the screen into. The span says how many of those chunks each view should take. This is pretty great, but I'd like my park cards to snap into place. The new scrollTargetLayout modifier makes that easy. I'll add it to the LazyHStack and modify the scroll view to align to views in the targeted layout. In addition to view alignment, scroll views can also be defined to use a paging behavior. And for a truly custom experience, you can define your own behavior using the scrollTargetBehavior protocol. I also thought my mutts would deserve a little accolade when they're at the top of the scroll view. The new scrollPosition modifier takes a binding to the topmost item's ID, and it's updated as I scroll. This way, I always know who's top dog. To learn more about all these and the other great improvements to Scroll View, be sure to watch "Beyond scroll views." Image now supports rendering content with high dynamic range. By applying the allowedDynamicRange modifier, the beautiful images in our app's gallery screen can be shown with their full fidelity. It's best to use this sparingly, though, and usually when the image stands alone. Apps written with SwiftUI work great with accessibility features out of the box, but you can make them even better with some of the new accessibility APIs we're introducing. The adventurous dog in this photo is a bit too far away to see, so I've applied a magnification gesture to allow zooming in. I'm also going to add the new accessibilityZoomAction modifier to my view. This allows assistive technologies like VoiceOver to access the same functionality without using the gesture. I'll just update the zoom level depending on the action's direction, and I can see what mischief she's been up to now. VoiceOver: Zooming image view. Image. Jeff: To learn more about all the new accessibility functionality across Apple's platforms, be sure to check out "Build accessible apps with SwiftUI and UIKit." Color now supports using static member syntax to look up custom colors defined in your app's asset catalog. This gives compile-time safety when using them, so you'll never lose time to a typo. For the document app I showed earlier, I've added a menu containing several useful actions to the toolbar. The top section of the menu is a ControlGroup with the new compactMenu style, which shows its items as icons in a horizontal stack. The tag color selector is defined as a picker with the new palette style. Using this style in concert with symbol images gives a great visual representation in menus, especially one like this where I can use the label's tint to differentiate them. Lastly, the paletteSelectionEffect modifier lets me use a symbol variant to represent the selected item in the picker. With my menu in place, Buddy's dog tag can now be his favorite color, tennis-ball yellow. Bordered buttons can now be defined with new built-in shapes, such as circle and rounded rectangle. These new border shape styles work on iOS, watchOS, and macOS. Buttons on macOS and iOS can now react to drag actions, like this button in my editor that opens a popover. The new springLoadingBehavior modifier indicates that a button should trigger its action when a drag pauses over it, or when force-clicking it on macOS. Buttons on tvOS can make great use of the new highlight hover effect. I've used it on our gallery images and applied it just to the image portion of my button's label, to create an effect which feels right at home on the platform. These buttons also use the borderless style, which is now available on tvOS. Hardware keyboards are great for providing an accelerant for common interactions in your app. Focusable views on platforms with hardware keyboard support can use the onKeyPress modifier to directly react to any keyboard input. The modifier takes a set of keys to match against and an action to perform for the event. To get your fill of focus-related recipes, be sure to watch "The SwiftUI cookbook for focus." From the scroll transitions and behaviors, to button styles and focus interactions, these new APIs can help you build apps with rich functionality and outstanding style. Curt: I think we've made great progress on our app! Jeff: It's certainly something. Curt: It was fun using all these new APIs. Jeff: That much is true. Curt: It's an exciting time for SwiftUI. There's a brand-new platform! Jeff: And the elegance of Observable and SwiftData work so well with SwiftUI. Curt: The animation improvements are amazing. Jeff: And don't forget scroll views! Curt: It's always a thrill to see what our amazing developer community can do with these new APIs. Jeff: Thanks for watching, everyone. Say hi to your dog for me! Curt: Keep doing great work! ♪
-
-
4:49 - watchOS 10
import SwiftUI #if os(watchOS) struct ContainerBackground_Snippet: View { @State private var selection: Int? @State var date = Date() var body: some View { NavigationSplitView { List(selection: $selection) { NavigationLink("Dates", value: -1) NavigationLink("Zero", value: 0) NavigationLink("One", value: 1) NavigationLink("Two", value: 2) } .containerBackground( Color.green.gradient, for: .navigation) } detail: { switch selection { case -1: DatePicker( "Time", selection: $date, displayedComponents: .hourMinuteAndSecond) .containerBackground( Color.yellow.gradient, for: .navigation) case let value?: DetailView(value: value) .containerBackground( Color.blue.gradient, for: .navigation) default: Text("Choose a link") } } } struct DetailView: View { var value: Int var body: some View { Text("\(value)") .font(.largeTitle) } } } #Preview { ContainerBackground_Snippet() } #endif
-
7:01 - Widget Previews
#Preview(as: .systemSmall) { CaffeineTrackerWidget() } timeline: { CaffeineLogEntry.log1 CaffeineLogEntry.log2 CaffeineLogEntry.log3 CaffeineLogEntry.log4 }
-
7:28 - SwiftUI Preview
#Preview("good dog") { ZStack(alignment: .bottom) { Rectangle() .fill(Color.blue.gradient) Text("Riley") .font(.largeTitle) .padding() .background(.thinMaterial, in: .capsule) .padding() } .ignoresSafeArea() }
-
7:33 - Mac Preview
import SwiftUI struct MacPreview_Snippet: View { @State private var drinks = Drink.sampleData @State private var selection: Drink? var body: some View { NavigationSplitView { List(drinks, selection: $selection) { drink in NavigationLink(drink.name, value: drink) } } detail: { if let selection { DrinkCard(drink: selection) } else { ContentUnavailableView( "Select a drink", systemImage: "cup.and.saucer.fill") } } } } struct DrinkCard: View { var drink: Drink var body: some View { ZStack(alignment: .top) { Rectangle() .fill(Color.blue.gradient) Text(drink.name) .padding([.leading, .trailing], 16) .padding([.top, .bottom], 4) .background(.thinMaterial, in: .capsule) .padding() } } } struct Drink: Identifiable, Hashable { let id = UUID() var name: String static let sampleData: [Drink] = [ Drink(name: "Cappuccino"), Drink(name: "Coffee"), Drink(name: "Espresso"), Drink(name: "Latte"), Drink(name: "Macchiato"), ] } #Preview { MacPreview_Snippet() }
-
8:18 - MapKit
import SwiftUI import MapKit struct Maps_Snippet: View { private let location = CLLocationCoordinate2D( latitude: CLLocationDegrees(floatLiteral: 37.3353), longitude: CLLocationDegrees(floatLiteral: -122.0097)) var body: some View { Map { Marker("Pond", coordinate: location) UserAnnotation() } .mapControls { MapUserLocationButton() MapCompass() } } } #Preview { Maps_Snippet() }
-
8:46 - Scrolling Charts
import SwiftUI import Charts struct ScrollingChart_Snippet: View { @State private var scrollPosition = SalesData.last365Days.first! @State private var selection: SalesData? var body: some View { VStack(alignment: .leading) { VStack(alignment: .leading) { Text(""" Scrolled to: \ \(scrollPosition.day, format: .dateTime.day().month().year()) """) Text(""" Selected: \ \(selection?.day ?? .now, format: .dateTime.day().month().year()) """) .opacity(selection != nil ? 1.0 : 0.0) } .padding([.leading, .trailing]) Chart { ForEach(SalesData.last365Days, id: \.day) { BarMark( x: .value("Day", $0.day, unit: .day), y: .value("Sales", $0.sales)) } .foregroundStyle(.blue) } .chartScrollableAxes(.horizontal) .chartXVisibleDomain(length: 3600 * 24 * 30) .chartScrollPosition(x: $scrollPosition) .chartXSelection(value: $selection) } } } struct SalesData: Plottable { var day: Date var sales: Int var primitivePlottable: Date { day } init?(primitivePlottable: Date) { self.day = primitivePlottable self.sales = 0 } init(day: Date, sales: Int) { self.day = day self.sales = sales } static let last365Days: [SalesData] = buildSalesData() private static func buildSalesData() -> [SalesData] { var result: [SalesData] = [] var date = Date.now for _ in 0..<365 { result.append(SalesData(day: date, sales: Int.random(in: 150...250))) date = Calendar.current.date( byAdding: .day, value: -1, to: date)! } return result.reversed() } } #Preview { ScrollingChart_Snippet() }
-
9:00 - Donut and Pie Charts
import SwiftUI import Charts struct DonutChart_Snippet: View { var sales = Bagel.salesData var body: some View { NavigationStack { Chart(sales, id: \.name) { element in SectorMark( angle: .value("Sales", element.sales), innerRadius: .ratio(0.6), angularInset: 1.5) .cornerRadius(5) .foregroundStyle(by: .value("Name", element.name)) } .padding() .navigationTitle("Bagel Sales") .toolbarTitleDisplayMode(.inlineLarge) } } } struct Bagel { var name: String var sales: Int static var salesData: [Bagel] = buildSalesData() static func buildSalesData() -> [Bagel] { [ Bagel(name: "Blueberry", sales: 60), Bagel(name: "Everything", sales: 120), Bagel(name: "Choc. Chip", sales: 40), Bagel(name: "Cin. Raisin", sales: 100), Bagel(name: "Plain", sales: 140), Bagel(name: "Onion", sales: 70), Bagel(name: "Sesame Seed", sales: 110), ] } } #Preview { DonutChart_Snippet() }
-
9:31 - StoreKit
import SwiftUI import StoreKit struct SubscriptionStore_Snippet { var body: some View { SubscriptionStoreView(groupID: passGroupID) { PassMarketingContent() .lightMarketingContentStyle() .containerBackground(for: .subscriptionStoreFullHeight) { SkyBackground() } } .backgroundStyle(.clear) .subscriptionStoreButtonLabel(.multiline) .subscriptionStorePickerItemBackground(.thinMaterial) .storeButton(.visible, for: .redeemCode) } }
-
10:56 - Observable Model
import Foundation import SwiftUI @Observable class Dog: Identifiable { var id = UUID() var name = "" var age = 1 var breed = DogBreed.mutt var owner: Person? = nil } class Person: Identifiable { var id = UUID() var name = "" } enum DogBreed { case mutt }
-
11:22 - Observable View Integration
import Foundation import SwiftUI struct DogCard: View { var dog: Dog var body: some View { DogImage(dog: dog) .overlay(alignment: .bottom) { HStack { Text(dog.name) Spacer() Image(systemName: "heart") .symbolVariant(dog.isFavorite ? .fill : .none) } .font(.headline) .padding(.horizontal, 22) .padding(.vertical, 12) .background(.thinMaterial) } .clipShape(.rect(cornerRadius: 16)) } struct DogImage: View { var dog: Dog var body: some View { Rectangle() .fill(Color.green) .frame(width: 400, height: 400) } } @Observable class Dog: Identifiable { var id = UUID() var name = "" var isFavorite = false } }
-
12:22 - Observable State Integration
import Foundation import SwiftUI struct AddSightingView: View { @State private var model = DogDetails() var body: some View { Form { Section { TextField("Name", text: $model.dogName) DogBreedPicker(selection: $model.dogBreed) } Section { TextField("Location", text: $model.location) } } } struct DogBreedPicker: View { @Binding var selection: DogBreed var body: some View { Picker("Breed", selection: $selection) { ForEach(DogBreed.allCases) { Text($0.rawValue.capitalized) .tag($0.id) } } } } @Observable class DogDetails { var dogName = "" var dogBreed = DogBreed.mutt var location = "" } enum DogBreed: String, CaseIterable, Identifiable { case mutt case husky case beagle var id: Self { self } } } #Preview { AddSightingView() }
-
12:33 - Observable Environment Integration
import SwiftUI @main private struct WhatsNew2023: App { @State private var currentUser: User? var body: some Scene { WindowGroup { ContentView() .environment(currentUser) } } struct ContentView: View { var body: some View { Color.clear } } struct ProfileView: View { @Environment(User.self) private var currentUser: User? var body: some View { if let currentUser { UserDetails(user: currentUser) } else { Button("Log In") { } } } } struct UserDetails: View { var user: User var body: some View { Text("Hello, \(user.name)") } } @Observable class User: Identifiable { var id = UUID() var name = "" } }
-
13:59 - SwiftData Model Container
import Foundation import SwiftUI import SwiftData @main private struct WhatsNew2023: App { var body: some Scene { WindowGroup { ContentView() } .modelContainer(for: Dog.self) } struct ContentView: View { var body: some View { Color.clear } } @Model class Dog { var name = "" var age = 1 } }
-
14:05 - SwiftData Query
import Foundation import SwiftUI import SwiftData struct RecentDogsView: View { @Query(sort: \.dateSpotted) private var dogs: [Dog] var body: some View { ScrollView(.vertical) { LazyVStack { ForEach(dogs) { dog in DogCard(dog: dog) } } } } struct DogCard: View { var dog: Dog var body: some View { DogImage(dog: dog) .overlay(alignment: .bottom) { HStack { Text(dog.name) Spacer() Image(systemName: "heart") .symbolVariant(dog.isFavorite ? .fill : .none) } .font(.headline) .padding(.horizontal, 22) .padding(.vertical, 12) .background(.thinMaterial) } .clipShape(.rect(cornerRadius: 16)) } } struct DogImage: View { var dog: Dog var body: some View { Rectangle() .fill(Color.green) .frame(width: 400, height: 400) } } @Model class Dog: Identifiable { var name = "" var isFavorite = false var dateSpotted = Date.now } } #Preview { RecentDogsView() }
-
14:52 - SwiftData DocumentGroup
import SwiftUI import SwiftData import UniformTypeIdentifiers @main private struct WhatsNew2023: App { var body: some Scene { DocumentGroup(editing: DogTag.self, contentType: .dogTag) { ContentView() } } struct ContentView: View { var body: some View { Color.clear } } @Model class DogTag { var text = "" } } extension UTType { static var dogTag: UTType { UTType(exportedAs: "com.apple.SwiftUI.dogTag") } }
-
15:33 - Inspector
import SwiftUI struct InspectorContentView: View { @State private var inspectorPresented = true var body: some View { DogTagEditor() .inspector(isPresented: $inspectorPresented) { DogTagInspector() } } struct DogTagEditor: View { var body: some View { Color.clear } } struct DogTagInspector: View { @State private var fontName = FontName.sfHello @State private var fontColor: Color = .white var body: some View { Form { Section("Text Formatting") { Picker("Font", selection: $fontName) { ForEach(FontName.allCases) { Text($0.name).tag($0) } } ColorPicker("Font Color", selection: $fontColor) } } } } enum FontName: Identifiable, CaseIterable { case sfHello case arial case helvetica var id: Self { self } var name: String { switch self { case .sfHello: return "SF Hello" case .arial: return "Arial" case .helvetica: return "Helvetica" } } } } #Preview { InspectorContentView() }
-
16:10 - File Export Dialog Customization
import Foundation import SwiftUI import UniformTypeIdentifiers struct ExportDialogCustomization: View { @State private var isExporterPresented = true @State private var selectedItem = "" var body: some View { Color.clear .fileExporter( isPresented: $isExporterPresented, item: selectedItem, contentTypes: [.plainText], defaultFilename: "ExportedData.txt") { result in handleDataExport(result: result) } .fileExporterFilenameLabel("Export Data") .fileDialogConfirmationLabel("Export Data") } func handleDataExport(result: Result<URL, Error>) { } struct Data: Codable, Transferable { static var transferRepresentation: some TransferRepresentation { CodableRepresentation(contentType: .plainText) } var text = "Exported Data" } }
-
16:19 - Confirmation Dialog Customization
import Foundation import SwiftUI import UniformTypeIdentifiers struct ConfirmationDialogCustomization: View { @State private var showDeleteDialog = false @AppStorage("dialogIsSuppressed") private var dialogIsSuppressed = false var body: some View { Button("Show Dialog") { if !dialogIsSuppressed { showDeleteDialog = true } } .confirmationDialog( "Are you sure you want to delete the selected dog tag?", isPresented: $showDeleteDialog) { Button("Delete dog tag", role: .destructive) { } HelpLink { } } .dialogSeverity(.critical) .dialogSuppressionToggle(isSuppressed: $dialogIsSuppressed) } }
-
17:01 - Table Column Customization
import SwiftUI struct DogSightingsTable: View { private var dogSightings: [DogSighting] = (1..<50).map { .init( name: "Sighting \($0)", date: .now + Double((Int.random(in: -5..<5) * 86400))) } @SceneStorage("columnCustomization") private var columnCustomization: TableColumnCustomization<DogSighting> @State private var selectedSighting: DogSighting.ID? var body: some View { Table( dogSightings, selection: $selectedSighting, columnCustomization: $columnCustomization) { TableColumn("Dog Name", value: \.name) .customizationID("name") TableColumn("Date") { Text($0.date, style: .date) } .customizationID("date") } } struct DogSighting: Identifiable { var id = UUID() var name: String var date: Date } }
-
17:22 - DisclosureTableRow
import SwiftUI struct DogGenealogyTable: View { private static let dogToys = ["🦴", "🧸", "👟", "🎾", "🥏"] private var dogs: [DogGenealogy] = (1..<10).map { .init( name: "Parent \($0)", age: Int.random(in: 8..<12) * 7, favoriteToy: dogToys[Int.random(in: 0..<5)], children: (1..<10).map { .init( name: "Child \($0)", age: Int.random(in: 1..<5) * 7, favoriteToy: dogToys[Int.random(in: 0..<5)]) } ) } var body: some View { Table(of: DogGenealogy.self) { TableColumn("Dog Name", value: \.name) TableColumn("Age (Dog Years)") { Text($0.age, format: .number) } TableColumn("Favorite Toy", value: \.favoriteToy) } rows: { ForEach(dogs) { dog in DisclosureTableRow(dog) { ForEach(dog.children) { child in TableRow(child) } } } } } struct DogGenealogy: Identifiable { var id = UUID() var name: String var age: Int var favoriteToy: String var children: [DogGenealogy] = [] } }
-
17:45 - Programmatic Section Expansion
import SwiftUI struct ExpandableSectionsView: View { @State private var selection: Int? var body: some View { NavigationSplitView { Sidebar(selection: $selection) } detail: { Detail(selection: selection) } } struct Sidebar: View { @Binding var selection: Int? @State private var isSection1Expanded = true @State private var isSection2Expanded = false var body: some View { List(selection: $selection) { Section("First Section", isExpanded: $isSection1Expanded) { ForEach(1..<6, id: \.self) { Text("Item \($0)") } } Section("Second Section", isExpanded: $isSection2Expanded) { ForEach(6..<11, id: \.self) { Text("Item \($0)") } } } } } struct Detail: View { var selection: Int? var body: some View { Text(selection.map { "Selection: \($0)" } ?? "No Selection") } } }
-
17:54 - Table Display Customization And Background Prominence
import SwiftUI struct TableDisplayCustomizationView: View { private var dogSightings: [DogSighting] = (1..<10).map { .init( name: "Dog Breed \($0)", sightings: Int.random(in: 1..<5), rating: Int.random(in: 1..<6)) } @State private var selection: DogSighting.ID? var body: some View { Table(dogSightings, selection: $selection) { TableColumn("Name", value: \.name) TableColumn("Sightings") { Text($0.sightings, format: .number) } TableColumn("Rating") { StarRating(rating: $0.rating) .foregroundStyle(.starRatingForeground) } } .alternatingRowBackgrounds(.disabled) .tableColumnHeaders(.hidden) } struct StarRating: View { var rating: Int var body: some View { HStack(spacing: 1) { ForEach(1...5, id: \.self) { n in Image(systemName: "star") .symbolVariant(n <= rating ? .fill : .none) } } .imageScale(.small) } } struct StarRatingForegroundStyle: ShapeStyle { func resolve(in environment: EnvironmentValues) -> some ShapeStyle { if environment.backgroundProminence == .increased { return AnyShapeStyle(.secondary) } else { return AnyShapeStyle(.yellow) } } } struct DogSighting: Identifiable { var id = UUID() var name: String var sightings: Int var rating: Int } } extension ShapeStyle where Self == TableDisplayCustomizationView.StarRatingForegroundStyle { static var starRatingForeground: TableDisplayCustomizationView.StarRatingForegroundStyle { .init() } }
-
19:19 - Keyframe Animator
import SwiftUI struct KeyframeAnimator_Snippet: View { var body: some View { Logo(color: .blue) Text("Tap the shape") } } struct Logo: View { var color: Color @State private var runPlan = 0 var body: some View { VStack(spacing: 100) { KeyframeAnimator( initialValue: AnimationValues(), trigger: runPlan ) { values in LogoField(color: color) .scaleEffect(values.scale) .rotationEffect(values.rotation, anchor: .bottom) .offset(y: values.verticalTranslation) .frame(width: 240, height: 240) } keyframes: { _ in KeyframeTrack(\.verticalTranslation) { SpringKeyframe(30, duration: 0.25, spring: .smooth) CubicKeyframe(-120, duration: 0.3) CubicKeyframe(-120, duration: 0.5) CubicKeyframe(10, duration: 0.3) SpringKeyframe(0, spring: .bouncy) } KeyframeTrack(\.scale) { SpringKeyframe(0.98, duration: 0.25, spring: .smooth) SpringKeyframe(1.2, duration: 0.5, spring: .smooth) SpringKeyframe(1.0, spring: .bouncy) } KeyframeTrack(\.rotation) { LinearKeyframe(Angle(degrees:0), duration: 0.45) CubicKeyframe(Angle(degrees: 0), duration: 0.1) CubicKeyframe(Angle(degrees: -15), duration: 0.1) CubicKeyframe(Angle(degrees: 15), duration: 0.1) CubicKeyframe(Angle(degrees: -15), duration: 0.1) SpringKeyframe(Angle(degrees: 0), spring: .bouncy) } } .onTapGesture { runPlan += 1 } } } struct AnimationValues { var scale = 1.0 var verticalTranslation = 0.0 var rotation = Angle(degrees: 0.0) } struct LogoField: View { var color: Color var body: some View { ZStack(alignment: .bottom) { RoundedRectangle(cornerRadius: 48) .fill(.shadow(.drop(radius: 5))) .fill(color.gradient) } } } } #Preview { KeyframeAnimator_Snippet() }
-
20:35 - Phase Animator
import SwiftUI struct PhaseAnimator_Snippet: View { @State private var sightingCount = 0 var body: some View { VStack { Spacer() HappyDog() .phaseAnimator( SightingPhases.allCases, trigger: sightingCount ) { content, phase in content .rotationEffect(phase.rotation) .scaleEffect(phase.scale) } animation: { phase in switch phase { case .shrink: .snappy(duration: 0.1) case .spin: .bouncy case .grow: .spring( duration: 0.2, bounce: 0.1, blendDuration: 0.1) case .reset: .linear(duration: 0.0) } } .sensoryFeedback(.increase, trigger: sightingCount) Spacer() Button("There’s One!", action: recordSighting) .zIndex(-1.0) } } func recordSighting() { sightingCount += 1 } enum SightingPhases: CaseIterable { case reset case shrink case spin case grow var rotation: Angle { switch self { case .spin, .grow: Angle(degrees: 360) default: Angle(degrees: 0) } } var scale: Double { switch self { case .reset: 1.0 case .shrink: 0.75 case .spin: 0.85 case .grow: 1.0 } } } } struct HappyDog: View { var body: some View { ZStack(alignment: .center) { Rectangle() .fill(.blue.gradient) Text("🐶") .font(.system(size: 58)) } .clipShape(.rect(cornerRadius: 12)) .frame(width: 96, height: 96) } } #Preview { PhaseAnimator_Snippet() }
-
22:27 - Haptic Feedback
https://developer.apple.com/design/human-interface-guidelines/playing-haptics
-
22:35 - Visual Effects
import SwiftUI struct VisualEffects_Snippet: View { @State private var dogs: [Dog] = manySampleDogs @StateObject private var simulation = Simulation() @State private var showFocalPoint = false var body: some View { ScrollView { LazyVGrid(columns: columns, spacing: itemSpacing) { ForEach(dogs) { dog in DogCircle(dog: dog, focalPoint: simulation.point) } } .opacity(showFocalPoint ? 0.3 : 1.0) .overlay(alignment: .topLeading) { DebugDot(focalPoint: simulation.point) .opacity(showFocalPoint ? 1.0 : 0.0) } .compositingGroup() } .coordinateSpace(.dogGrid) .onTapGesture { withAnimation { showFocalPoint.toggle() } } } var columns: [GridItem] { [GridItem( .adaptive( minimum: imageLength, maximum: imageLength ), spacing: itemSpacing )] } struct DebugDot: View { var focalPoint: CGPoint var body: some View { Circle() .fill(.red) .frame(width: 10, height: 10) .visualEffect { content, proxy in content.offset(position(in: proxy)) } } func position(in proxy: GeometryProxy) -> CGSize { guard let backgroundSize = proxy.bounds(of: .dogGrid)?.size else { return .zero } let frame = proxy.frame(in: .dogGrid) let center = CGPoint( x: (frame.minX + frame.maxX) / 2.0, y: (frame.minY + frame.maxY) / 2.0 ) let xOffset = focalPoint.x * backgroundSize.width - center.x let yOffset = focalPoint.y * backgroundSize.height - center.y return CGSize(width: xOffset, height: yOffset) } } /// A self-updating simulation of a point bouncing inside a unit square. @MainActor class Simulation: ObservableObject { @Published var point = CGPoint( x: Double.random(in: 0.001..<1.0), y: Double.random(in: 0.001..<1.0) ) private var velocity = CGVector(dx: 0.0048, dy: 0.0028) private var updateTask: Task<Void, Never>? private var isUpdating = true init() { updateTask = Task.detached { do { while true { try await Task.sleep(for: .milliseconds(16)) await self.updateLocation() } } catch { // fallthrough and exit } } } func toggle() { isUpdating.toggle() } private func updateLocation() { guard isUpdating else { return } point.x += velocity.dx point.y += velocity.dy if point.x < 0 || point.x >= 1.0 { velocity.dx *= -1 point.x += 2 * velocity.dx } if point.y < 0 || point.y >= 1.0 { velocity.dy *= -1 point.y += 2 * velocity.dy } } } } extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace { fileprivate static var dogGrid: Self { .named("dogGrid") } } private func magnitude(dx: Double, dy: Double) -> Double { sqrt(dx * dx + dy * dy) } private struct DogCircle: View { var dog: Dog var focalPoint: CGPoint var body: some View { ZStack { DogImage(dog: dog) .visualEffect { content, geometry in content .scaleEffect(contentScale(in: geometry)) .saturation(contentSaturation(in: geometry)) .opacity(contentOpacity(in: geometry)) } } } } private struct DogImage: View { var dog: Dog var body: some View { Circle() .fill(.shadow(.drop( color: .black.opacity(0.4), radius: 4, x: 0, y: 2))) .fill(dog.color) .strokeBorder(.secondary, lineWidth: 3) .frame(width: imageLength, height: imageLength) } } extension DogCircle { func contentScale(in geometry: GeometryProxy) -> Double { guard let gridSize = geometry.bounds(of: .dogGrid)?.size else { return 0 } let frame = geometry.frame(in: .dogGrid) let center = CGPoint(x: (frame.minX + frame.maxX) / 2.0, y: (frame.minY + frame.maxY) / 2.0) let xOffset = focalPoint.x * gridSize.width - center.x let yOffset = focalPoint.y * gridSize.height - center.y let unitMagnitude = magnitude(dx: xOffset, dy: yOffset) / magnitude(dx: gridSize.width, dy: gridSize.height) if unitMagnitude < 0.2 { let d = 3 * (unitMagnitude - 0.2) return 1.0 + 1.2 * d * d * (1 + d) } else { return 1.0 } } func contentOpacity(in geometry: GeometryProxy) -> Double { opacity(for: displacement(in: geometry)) } func contentSaturation(in geometry: GeometryProxy) -> Double { opacity(for: displacement(in: geometry)) } func opacity(for displacement: Double) -> Double { if displacement < 0.3 { return 1.0 } else { return 1.0 - (displacement - 0.3) * 1.43 } } func displacement(in proxy: GeometryProxy) -> Double { guard let backgroundSize = proxy.bounds(of: .dogGrid)?.size else { return 0 } let frame = proxy.frame(in: .dogGrid) let center = CGPoint( x: (frame.minX + frame.maxX) / 2.0, y: (frame.minY + frame.maxY) / 2.0 ) let xOffset = focalPoint.x * backgroundSize.width - center.x let yOffset = focalPoint.y * backgroundSize.height - center.y return magnitude(dx: xOffset, dy: yOffset) / magnitude( dx: backgroundSize.width, dy: backgroundSize.height) } } private struct Dog: Identifiable { let id = UUID() var color: Color } private let imageLength = 100.0 private let itemSpacing = 20.0 private let possibleColors: [Color] = [.red, .orange, .yellow, .green, .blue, .indigo, .purple] private let manySampleDogs: [Dog] = (0..<100).map { Dog(color: possibleColors[$0 % possibleColors.count]) } #Preview { VisualEffects_Snippet() }
-
23:39 - Metal Shader
import SwiftUI struct ShaderUse_Snippet: View { @State private var stripeSpacing: Float = 10.0 @State private var stripeAngle: Float = 0.0 var body: some View { VStack { Text( """ \( Text("Furdinand") .foregroundStyle(stripes) .fontWidth(.expanded) ) \ is a good dog! """ ) .font(.system(size: 56, weight: .heavy).width(.condensed)) .lineLimit(...4) .multilineTextAlignment(.center) Spacer() controls Spacer() } .padding() } var stripes: Shader { ShaderLibrary.angledFill( .float(stripeSpacing), .float(stripeAngle), .color(.blue) ) } @ViewBuilder var controls: some View { Grid(alignment: .trailing) { GridRow { spacingSlider ZStack(alignment: .trailing) { Text("50.0 PX").hidden() // maintains size Text(""" \(stripeSpacing, format: .number.precision(.fractionLength(1))) \ \(Text("PX").textScale(.secondary)) """) .foregroundStyle(.secondary) } } GridRow { angleSlider ZStack(alignment: .trailing) { Text("-0.09π RAD").hidden() // maintains size Text(""" \(stripeAngle / .pi, format: .number.precision(.fractionLength(2)))π \ \(Text("RAD").textScale(.secondary)) """) .foregroundStyle(.secondary) } } } .labelsHidden() } @ViewBuilder var spacingSlider: some View { Slider( value: $stripeSpacing, in: Float(10.0)...50.0) { Text("Spacing") } minimumValueLabel: { Image( systemName: "arrow.down.forward.and.arrow.up.backward") } maximumValueLabel: { Image( systemName: "arrow.up.backward.and.arrow.down.forward") } } @ViewBuilder var angleSlider: some View { Slider( value: $stripeAngle, in: (-.pi / 2)...(.pi / 2)) { Text("Angle") } minimumValueLabel: { Image( systemName: "arrow.clockwise") } maximumValueLabel: { Image( systemName: "arrow.counterclockwise") } } } // NOTE: create a .metal file in your project and add the following to it: /* #include <metal_stdlib> using namespace metal; [[ stitchable ]] half4 angledFill(float2 position, float width, float angle, half4 color) { float pMagnitude = sqrt(position.x * position.x + position.y * position.y); float pAngle = angle + (position.x == 0.0f ? (M_PI_F / 2.0f) : atan(position.y / position.x)); float rotatedX = pMagnitude * cos(pAngle); float rotatedY = pMagnitude * sin(pAngle); return (color + color * fmod(abs(rotatedX + rotatedY), width) / width) / 2; } */ #Preview { ShaderUse_Snippet() }
-
25:01 - Symbol Effect
import SwiftUI struct SymbolEffect_Snippet: View { @State private var downloadCount = -2 @State private var isPaused = false var scaleUpActive: Bool { (downloadCount % 2) == 0 } var isHidden: Bool { scaleUpActive } var isShown: Bool { scaleUpActive } var isPlaying: Bool { scaleUpActive } var body: some View { ScrollView { VStack(spacing: 48) { Image(systemName: "rectangle.inset.filled.and.person.filled") .symbolEffect(.pulse) .frame(maxWidth: .infinity) Image(systemName: "arrow.down.circle") .symbolEffect(.bounce, value: downloadCount) Image(systemName: "wifi") .symbolEffect(.variableColor.iterative.reversing) Image(systemName: "bubble.left.and.bubble.right.fill") .symbolEffect(.scale.up, isActive: scaleUpActive) Image(systemName: "cloud.sun.rain.fill") .symbolEffect(.disappear, isActive: isHidden) Image(systemName: isPlaying ? "play.fill" : "pause.fill") .contentTransition(.symbolEffect(.replace.downUp)) } .padding() } .font(.system(size: 64)) .frame(maxWidth: .infinity) .symbolRenderingMode(.multicolor) .preferredColorScheme(.dark) .task { do { while true { try await Task.sleep(for: .milliseconds(1500)) if !isPaused { downloadCount += 1 } } } catch { print("exiting") } } } } #Preview { SymbolEffect_Snippet() }
-
25:35 - Metal Shader (cont.)
import SwiftUI struct ShaderUse_Snippet: View { @State private var stripeSpacing: Float = 10.0 @State private var stripeAngle: Float = 0.0 var body: some View { VStack { Text( """ \( Text("Furdinand") .foregroundStyle(stripes) .fontWidth(.expanded) ) \ is a good dog! """ ) .font(.system(size: 56, weight: .heavy).width(.condensed)) .lineLimit(...4) .multilineTextAlignment(.center) Spacer() controls Spacer() } .padding() } var stripes: Shader { ShaderLibrary.angledFill( .float(stripeSpacing), .float(stripeAngle), .color(.blue) ) } @ViewBuilder var controls: some View { Grid(alignment: .trailing) { GridRow { spacingSlider ZStack(alignment: .trailing) { Text("50.0 PX").hidden() // maintains size Text(""" \(stripeSpacing, format: .number.precision(.fractionLength(1))) \ \(Text("PX").textScale(.secondary)) """) .foregroundStyle(.secondary) } } GridRow { angleSlider ZStack(alignment: .trailing) { Text("-0.09π RAD").hidden() // maintains size Text(""" \(stripeAngle / .pi, format: .number.precision(.fractionLength(2)))π \ \(Text("RAD").textScale(.secondary)) """) .foregroundStyle(.secondary) } } } .labelsHidden() } @ViewBuilder var spacingSlider: some View { Slider( value: $stripeSpacing, in: Float(10.0)...50.0) { Text("Spacing") } minimumValueLabel: { Image( systemName: "arrow.down.forward.and.arrow.up.backward") } maximumValueLabel: { Image( systemName: "arrow.up.backward.and.arrow.down.forward") } } @ViewBuilder var angleSlider: some View { Slider( value: $stripeAngle, in: (-.pi / 2)...(.pi / 2)) { Text("Angle") } minimumValueLabel: { Image( systemName: "arrow.clockwise") } maximumValueLabel: { Image( systemName: "arrow.counterclockwise") } } } // NOTE: create a .metal file in your project and add the following to it: /* #include <metal_stdlib> using namespace metal; [[ stitchable ]] half4 angledFill(float2 position, float width, float angle, half4 color) { float pMagnitude = sqrt(position.x * position.x + position.y * position.y); float pAngle = angle + (position.x == 0.0f ? (M_PI_F / 2.0f) : atan(position.y / position.x)); float rotatedX = pMagnitude * cos(pAngle); float rotatedY = pMagnitude * sin(pAngle); return (color + color * fmod(abs(rotatedX + rotatedY), width) / width) / 2; } */ #Preview { ShaderUse_Snippet() }
-
26:11 - Typesetting Language
import SwiftUI struct TypesettingLanguage_Snippet: View { var dog = Dog( name: "ไมโล", language: .init(languageCode: .thai), imageName: "Puppy_Pitbull") func phrase(for name: Text) -> Text { Text( "Who's a good dog, \(name)?" ) } var body: some View { HStack(spacing: 54) { VStack { phrase(for: Text("Milo")) } VStack { phrase(for: Text(dog.name)) } VStack { phrase(for: dog.nameText) } } .font(.title) .lineLimit(...5) .multilineTextAlignment(.leading) .padding() } struct Dog { var name: String var language: Locale.Language var imageName: String var nameText: Text { Text(name).typesettingLanguage(language) } } } #Preview { TypesettingLanguage_Snippet() }
-
27:46 - ScrollView Transitions And Behaviors
import SwiftUI struct ScrollingRecentDogsView: View { private static let colors: [Color] = [.red, .blue, .brown, .yellow, .purple] private var dogs: [Dog] = (1..<10).map { .init( name: "Dog \($0)", color: colors[Int.random(in: 0..<5)], isFavorite: false) } private var parks: [Park] = (1..<10).map { .init(name: "Park \($0)") } @State private var scrolledID: Dog.ID? var body: some View { ScrollView { LazyVStack { ForEach(dogs) { dog in DogCard(dog: dog, isTop: scrolledID == dog.id) .scrollTransition { content, phase in content .scaleEffect(phase.isIdentity ? 1 : 0.8) .opacity(phase.isIdentity ? 1 : 0) } } } } .scrollPosition(id: $scrolledID) .safeAreaInset(edge: .top) { ScrollView(.horizontal) { LazyHStack { ForEach(parks) { park in ParkCard(park: park) .aspectRatio(3.0 / 2.0, contentMode: .fill) .containerRelativeFrame( .horizontal, count: 5, span: 2, spacing: 8) } } .scrollTargetLayout() } .scrollTargetBehavior(.viewAligned) .padding(.vertical, 8) .fixedSize(horizontal: false, vertical: true) .background(.thinMaterial) } .safeAreaPadding(.horizontal, 16.0) } struct DogCard: View { var dog: Dog var isTop: Bool var body: some View { DogImage(dog: dog) .overlay(alignment: .bottom) { HStack { Text(dog.name) Spacer() if isTop { TopDog() } Spacer() Image(systemName: "heart") .symbolVariant(dog.isFavorite ? .fill : .none) } .font(.headline) .padding(.horizontal, 22) .padding(.vertical, 12) .background(.thinMaterial) } .clipShape(.rect(cornerRadius: 16)) } } struct DogImage: View { var dog: Dog var body: some View { Rectangle() .fill(dog.color.gradient) .frame(height: 400) } } struct TopDog: View { var body: some View { HStack { Image(systemName: "trophy.fill") Text("Top Dog") Image(systemName: "trophy.fill") } } } struct ParkCard: View { var park: Park var body: some View { RoundedRectangle(cornerRadius: 8) .fill(.green.gradient) .overlay { Text(park.name) .padding() } } } struct Dog: Identifiable { var id = UUID() var name: String var color: Color var isFavorite: Bool } struct Park: Identifiable { var id = UUID() var name: String } }
-
31:12 - Menu Enhancements
import SwiftUI struct DogTagEditMenu: View { @State private var selectedColor = TagColor.blue var body: some View { Menu { ControlGroup { Button { } label: { Label("Cut", systemImage: "scissors") } Button { } label: { Label("Copy", systemImage: "doc.on.doc") } Button { } label: { Label("Paste", systemImage: "doc.on.clipboard.fill") } Button { } label: { Label("Duplicate", systemImage: "plus.square.on.square") } } .controlGroupStyle(.compactMenu) Picker("Tag Color", selection: $selectedColor) { ForEach(TagColor.allCases) { Label($0.rawValue.capitalized, systemImage: "tag") .tint($0.color) .tag($0) } } .paletteSelectionEffect(.symbolVariant(.fill)) .pickerStyle(.palette) } label: { Label("Edit", systemImage: "ellipsis.circle") } .menuStyle(.button) } enum TagColor: String, CaseIterable, Identifiable { case blue case brown case green case yellow var id: Self { self } var color: Color { switch self { case .blue: return .blue case .brown: return .brown case .green: return .green case .yellow: return .yellow } } } }
-
32:30 - Highlight Hover Effect
import SwiftUI struct DogGalleryCard: View { @FocusState private var isFocused: Bool var body: some View { Button { } label: { VStack { RoundedRectangle(cornerRadius: 8) .fill(.blue) .frame(width: 888, height: 500) .hoverEffect(.highlight) Text("Name") .opacity(isFocused ? 1 : 0) } } .buttonStyle(.borderless) .focused($isFocused) } }
-
-
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.