-
Nouveautés de SwiftUI
Explorez les dernières nouveautés de SwiftUI et voyez comment elles peuvent améliorer vos apps. Nous présentons un nouveau protocole Document doté d'un accès direct au disque et d'un système de comparaison basé sur des instantanés pour concevoir des apps haute performance ; de nouvelles API pour réorganiser le contenu dans des listes, des grilles et des sections ; et des améliorations pour les barres d'outils, notamment la priorité de visibilité et un comportement de réduction automatique. Nous abordons également les nouvelles capacités des API de présentation, comme les gestes de balayage sur n'importe quelle vue, ainsi que les améliorations du cache AsyncImage et l'initialisation différée de l'état pour les types Observable.
Chapitres
- 0:00 - Introduction
- 2:12 - Apparence et expérience utilisateur actualisées
- 8:06 - Apps basées sur des documents
- 15:18 - Présentation et interaction
- 19:58 - Flux de données et performances
- 27:25 - Étapes suivantes
Ressources
- TN3211: Resolving SwiftUI source incompatibilities for State and ContentBuilder
- State()
- ContentBuilder
- Swift Collections on GitHub
Vidéos connexes
WWDC26
-
Rechercher dans cette vidéo…
-
-
3:20 - appearsActive environment value
struct SidebarFooterView: View { @Environment(\.appearsActive) private var appearsActive var body: some View { MyAccountView() .opacity(appearsActive ? 1 : 0.5) } } -
3:34 - Menu icon visibility
CommandMenu("Stickers") { Button { openStore() } label: { Label("Store", systemImage: "bag.fill") .labelStyle(.titleAndIcon) } } // Other menu items } -
5:12 - Prominent tab role
TabView { Tab { EventsTab() } Tab { HolidaysTab() } Tab { FunTab() } Tab(role: .prominent) { CartTab() } } -
6:15 - Toolbar item visibility and overflow menu
// Toolbar item visibility priority StickerPageView() .toolbar { ToolbarItemGroup { UndoButton() RedoButton() } .visibilityPriority(.high) ToolbarOverflowMenu { ChoosePhotoButton() ExportAsImageButton() ClearAllStickersButton() } ToolbarItem(placement: .topBarPinnedTrailing) { ShareButton() } } -
7:37 - Minimize toolbar on scroll with toolbarMinimizeBehavior
// Minimize toolbar when scrolling ScrollView { StickerListView() } .toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar) -
9:47 - Document creation sources with context parameter
// Use the context to create a document @main struct Stickers: App { var body: some Scene { DocumentGroupLaunchScene("Create a Sticker Page") { NewDocumentButton("New Sticker Page", source: .blank) NewDocumentButton("Sticker Page from Photo…", source: .photo) } DocumentGroup { /* ... */ } } } extension DocumentCreationSource { static let blank = Self(id: "blank") static let photo = Self(id: "photo") } -
10:01 - Use the context to create a document
@main struct Stickers: App { var body: some Scene { DocumentGroupLaunchScene("Create a Sticker Page") { NewDocumentButton("New Sticker Page", source: .blank) NewDocumentButton("Sticker Page from Photo…", source: .photo) } DocumentGroup { document in StickerPageDocumentView(document) } { configuration, context in StickerPageDocument(configuration: configuration, context: context) } } } -
10:43 - Document app declaration
@main struct Stickers: App { var body: some Scene { DocumentGroup { /* ... */ } WindowGroup { /* ... */ } } } -
11:25 - Implement document writing
@Observable final class StickerDocument { // ... } -
11:34 - Implement document writing: list writable formats
@Observable final class StickerDocument { static let writableDocumentTypes: [UTType] = [.stickerDocument] // ... } import UniformTypeIdentifiers extension UTType { static let stickerDocument = UTType(exportedAs: "stickerdocument") } -
11:45 - Implement document writing: provide snapshot
@Observable final class StickerDocument { static let writableDocumentTypes: [UTType] = [.stickerDocument] @MainActor func snapshot(contentType: UTType) async throws -> sending PageSnapshot { /* ... */ } // ... } -
11:54 - Implement document writing: represent the snapshot
struct PageSnapshot { var background: Image var metadata: StickerPlacements var stickers: [Image] } struct StickerPlacements { /* ... */ } -
12:13 - Implement document writing: provide a DocumentWriter
@Observable final class StickerDocument { static let writableDocumentTypes: [UTType] = [.stickerDocument] @MainActor func snapshot(contentType: UTType) async throws -> sending PageSnapshot { makeSnapshot() } func writer(configuration: sending WriteConfiguration) -> sending Writer { Writer(contentType: configuration.contentType) } } -
12:33 - DocumentWriter: Snapshot
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot // ... } -
12:36 - DocumentWriter: PageSnapshot as Snapshot
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType // ... } -
12:42 - DocumentWriter protocol implementation
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { // write .stickerDocument } } -
13:18 - Progress reporting during writing
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { // report progress… // write .stickerDocument } } -
13:27 - Implement document reading with ReadableDocument protocol
extension StickerDocument: ReadableDocument { } -
14:35 - Add PNG to supported formats list
@Observable final class StickerDocument: WritableDocument { static let writableContentTypes: [UTType] = [.stickerDocument, .png] } -
14:48 - Add content type checks
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { if contentType.conforms(to: .stickerDocument) { // write .stickerDocument } else if contentType.conforms(to: .png) } } -
14:56 - Writing multiple formats including PNG
struct Writer<Snapshot>: DocumentWriter { typealias Snapshot = PageSnapshot let contentType: UTType nonisolated func write( snapshot: sending PageSnapshot, to destination: URL, previous: sending PageSnapshot?, progress: consuming Subprogress ) async throws { if contentType.conforms(to: .stickerDocument) { // write .stickerDocument } else if contentType.conforms(to: .png) { let context = CGContext(/* ... */) context.draw(/* ... */) } } } -
15:58 - Reorderable list with reorderContainer
List { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) } .reorderable() } .reorderContainer(for: Sticker.self) { difference in difference.apply(to: &stickers) } -
16:14 - Apply changes to a reorderable list's data source
import OrderedCollections // from https://github.com/apple/swift-collections extension ReorderDifference where CollectionID == ReorderableSingleCollectionIdentifier { func apply(to values: inout [some Identifiable<ItemID>]) { var dictionary = OrderedDictionary(uniqueKeys: values.map { $0.id }, values: values) let destinationOffset: Int? = switch destination.position { case .before(let destination): dictionary.keys.firstIndex(of: destination) case .end: nil } dictionary.move(keys: sources, to: destinationOffset ?? values.endIndex) values = dictionary.values.elements } } -
16:48 - Reorderable grid with LazyVGrid
LazyVGrid { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) } .reorderable() } .reorderContainer(for: Sticker.self) { difference in difference.apply(to: &stickers) } -
18:12 - Swipe actions on List
List { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) .swipeActions { DeleteButton(sticker: sticker) } } } -
18:15 - Swipe actions on any view
ScrollView { LazyVStack { ForEach(stickers) { sticker in StickerListItemView(sticker: sticker) .swipeActions { DeleteButton(sticker: sticker) } } } } .swipeActionsContainer() -
18:54 - Confirmation dialog with item binding
struct StickerCanvasView: View { var stickers: [Sticker] @State private var stickerToDelete: Sticker? var body: some View { ZStack { ForEach(stickers) { sticker in PlacedStickerView(sticker: sticker) .contextMenu { // ... } } } .confirmationDialog( "Delete?", item: $stickerToDelete ) { sticker in DeleteStickerButton(sticker) } } } -
19:35 - Alert with item binding
struct StickerCanvasView: View { var stickers: [Sticker] @State private var stickerToDelete: Sticker? var body: some View { ZStack { ForEach(stickers) { sticker in PlacedStickerView(sticker: sticker) .contextMenu { // ... } } } .alert( "Delete?", item: $stickerToDelete ) { sticker in DeleteStickerButton(sticker) } } } -
21:18 - AsyncImage with URLRequest and custom URLSession
@Observable class StickerStore { static let imageSession: URLSession = { let config = URLSessionConfiguration.default config.urlCache = URLCache( memoryCapacity: 64 * 1024 * 1024, diskCapacity: 256 * 1024 * 1024) return URLSession(configuration: config) }() } ForEach(pets) { pet in AsyncImage(request: URLRequest( url: pet.imageURL, cachePolicy: .returnCacheDataElseLoad) ) } .asyncImageURLSession(StickerStore.imageSession) -
23:08 - @State converted to macro for lazy initialization
@Observable class StickerStore { } struct StickerStoreView: View { // store is now lazily initialized, only // created once for the lifetime of the view @State private var store = StickerStore() var body: some View { // ... } } -
23:48 - @State macro init assignment error
struct StickerPageView: View { @State private var page = StickerPage() let title: String init(title: String) { self.page = StickerPage(title: title) // Variable 'self.title' used before being initialized self.title = title } var body: some View { // ... } } -
24:02 - Fixed @State macro init assignment error
struct StickerPageView: View { @State private var page: StickerPage // Removed default value to fix error let title: String init(title: String) { self.page = StickerPage(title: title) self.title = title } var body: some View { // ... } } -
26:07 - @ContentBuilder
@ContentBuilder func stickerLibraryView() -> some View { // ... }
-