-
SwiftUIの新機能
SwiftUIに追加された最新の機能と、それらによってアプリをどのように改善できるかを確認しましょう。直接のディスクアクセスやスナップショットベースの差分処理により高性能なアプリの構築を可能にする新しいDocumentプロトコル、リスト/グリッド/セクションのコンテンツを並べ替えるための新しいAPI、表示の優先度設定や自動最小化の挙動などを実現するツールバーの機能強化を紹介します。拡張されたプレゼンテーションのためのAPI(任意のビューでのスワイプアクションなどの機能を追加)、AsyncImageのキャッシュの改善、Observable型の遅延状態初期化についても取り上げます。
関連する章
- 0:00 - Introduction
- 2:12 - Refreshed look and feel
- 8:06 - Document-based apps
- 15:18 - Presentation and interaction
- 19:58 - Data flow and performance
- 27:25 - Next steps
リソース
関連ビデオ
WWDC26
-
このビデオを検索
-
-
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 { // ... }
-
-
- 0:00 - Introduction
This video covers the refreshed Liquid Glass look and feel, new document-based app APIs, presentation and interaction improvements, and data flow and performance enhancements.
- 2:12 - Refreshed look and feel
Apps automatically adopt the updated Liquid Glass appearance on 2027 OS releases without code changes. Covers interactive Liquid Glass elements, the inactive window appearance on iPadOS, toolbar customization with overflow menus and pinned placements, minimize-on-scroll behavior, and guidance for building resizable apps using size classes.
- 8:06 - Document-based apps
Expanded document APIs for SwiftUI apps, including the new DocumentCreationSource API for custom new-document flows, performance improvements for reading and writing large documents, and first-class support for direct document URL access via the FileDocument and ReferenceFileDocument protocols.
- 15:18 - Presentation and interaction
New reorderable container APIs let users drag to reorder items in any container — List, LazyVGrid, and on watchOS for the first time. Also covers swipe actions on arbitrary views beyond List, and item binding-driven confirmation dialogs.
- 19:58 - Data flow and performance
AsyncImage now supports standard HTTP caching by default, with new APIs for custom URLRequest and URLSession configurations. The @State property wrapper is converted to a macro, making class initialization lazy to prevent redundant allocations — back-ported to iOS 17, macOS 14, and aligned releases.
- 27:25 - Next steps
Key takeaways and recommended next steps: build in Xcode 27 to see the updated Liquid Glass look in your app, adopt the new Document APIs for document-based apps, and explore the SwiftUI agent skills.