-
SwiftUI의 새로운 기능
SwiftUI에 추가된 최신 기능을 살펴보고, 이러한 기능이 앱을 어떻게 향상할 수 있는지 알아보세요. 고성능 앱 빌드를 위한 직접 디스크 접근과 스냅샷 기반 비교 기능을 갖춘 새로운 Document 프로토콜과 리스트, 그리드 및 섹션의 콘텐츠를 리오더할 수 있는 새로운 API, 가시성 우선순위 및 자동 최소화 동작을 포함한 도구 막대 개선 사항을 소개합니다. 또한 모든 뷰에서의 쓸어넘기기 동작과 AsyncImage 캐싱 개선 사항 및 Observable 유형에 대한 지연 상태 초기화 등 확장된 프레젠테이션 API도 다룹니다.
챕터
- 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.