2026 App ServicesDesignSwiftUI & UI Frameworks
WWDC26 · 28 min · App Services / Design / SwiftUI & UI Frameworks
What’s new in SwiftUI
Explore the latest additions to SwiftUI and discover how they can improve your apps. We’ll introduce a new Document protocol with direct disk access and snapshot-based diffing for building high-performance apps; new APIs for reordering content in lists, grids, and sections; and toolbar enhancements including visibility priority and auto-minimizing behavior. We’ll also cover expanded presentation APIs — including swipe actions on any view — plus AsyncImage caching improvements and lazy state initialization for Observable types.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 33 snippets
appearsActive environment value
struct SidebarFooterView: View {
(\.appearsActive) private var appearsActive
var body: some View {
MyAccountView()
.opacity(appearsActive ? 1 : 0.5)
}
} Menu icon visibility
CommandMenu("Stickers") {
Button { openStore() } label: {
Label("Store", systemImage: "bag.fill")
.labelStyle(.titleAndIcon)
}
}
// Other menu items
} Prominent tab role
TabView {
Tab { EventsTab() }
Tab { HolidaysTab() }
Tab { FunTab() }
Tab(role: .prominent) {
CartTab()
}
} 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()
}
} Minimize toolbar on scroll with toolbarMinimizeBehavior
// Minimize toolbar when scrolling
ScrollView {
StickerListView()
}
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar) 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")
} 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)
}
}
} Document app declaration
@main
struct Stickers: App {
var body: some Scene {
DocumentGroup { /* ... */ }
WindowGroup { /* ... */ }
}
} Implement document writing
final class StickerDocument {
// ...
} Implement document writing: list writable formats
final class StickerDocument {
static let writableDocumentTypes: [UTType] = [.stickerDocument]
// ...
}
import UniformTypeIdentifiers
extension UTType {
static let stickerDocument = UTType(exportedAs: "stickerdocument")
} Implement document writing: provide snapshot
final class StickerDocument {
static let writableDocumentTypes: [UTType] = [.stickerDocument]
func snapshot(contentType: UTType) async throws -> sending PageSnapshot { /* ... */ }
// ...
} Implement document writing: represent the snapshot
struct PageSnapshot {
var background: Image
var metadata: StickerPlacements
var stickers: [Image]
}
struct StickerPlacements { /* ... */ } Implement document writing: provide a DocumentWriter
final class StickerDocument {
static let writableDocumentTypes: [UTType] = [.stickerDocument]
func snapshot(contentType: UTType) async throws -> sending PageSnapshot {
makeSnapshot()
}
func writer(configuration: sending WriteConfiguration) -> sending Writer {
Writer(contentType: configuration.contentType)
}
} DocumentWriter: Snapshot
struct Writer<Snapshot>: DocumentWriter {
typealias Snapshot = PageSnapshot
// ...
} DocumentWriter: PageSnapshot as Snapshot
struct Writer<Snapshot>: DocumentWriter {
typealias Snapshot = PageSnapshot
let contentType: UTType
// ...
} 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
}
} 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
}
} Implement document reading with ReadableDocument protocol
extension StickerDocument: ReadableDocument {
} Add PNG to supported formats list
final class StickerDocument: WritableDocument {
static let writableContentTypes: [UTType] = [.stickerDocument, .png]
} 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)
}
} 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(/* ... */)
}
}
} Reorderable list with reorderContainer
List {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
}
.reorderable()
}
.reorderContainer(for: Sticker.self) { difference in
difference.apply(to: &stickers)
} 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
}
} Reorderable grid with LazyVGrid
LazyVGrid {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
}
.reorderable()
}
.reorderContainer(for: Sticker.self) { difference in
difference.apply(to: &stickers)
} Swipe actions on List
List {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
.swipeActions {
DeleteButton(sticker: sticker)
}
}
} Swipe actions on any view
ScrollView {
LazyVStack {
ForEach(stickers) { sticker in
StickerListItemView(sticker: sticker)
.swipeActions {
DeleteButton(sticker: sticker)
}
}
}
}
.swipeActionsContainer() Confirmation dialog with item binding
struct StickerCanvasView: View {
var stickers: [Sticker]
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)
}
}
} Alert with item binding
struct StickerCanvasView: View {
var stickers: [Sticker]
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)
}
}
} AsyncImage with URLRequest and custom URLSession
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) @State converted to macro for lazy initialization
class StickerStore { }
struct StickerStoreView: View {
// store is now lazily initialized, only
// created once for the lifetime of the view
private var store = StickerStore()
var body: some View {
// ...
}
} @State macro init assignment error
struct StickerPageView: View {
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 {
// ...
}
} Fixed @State macro init assignment error
struct StickerPageView: View {
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 {
// ...
}
} @ContentBuilder
func stickerLibraryView() -> some View {
// ...
} Resources
Related sessions
-
16 min -
15 min