WWDC Quick Look 💓 By SwiftGGTeam
What's new in SwiftUI

What's new in SwiftUI

观看原视频

Highlight

SwiftUI 在 2027 平台版本中引入了全新的 Document API(支持直接磁盘访问和快照差异写入)、可重排序容器、Toolbar 可见性优先级与自动最小化、任意视图的滑动手势,以及 @State 宏化和 ContentBuilder 带来的编译性能提升。

核心内容

Liquid Glass 与窗口状态响应

在 2027 版本中,SwiftUI 应用自动获得 Liquid Glass 的更新外观,无需修改任何代码。(02:17)macOS 上的 Liquid Glass 自定义元素可以标记为 interactive,对鼠标点击的响应更加流畅。iPad 应用在窗口非活跃时,图标和文字会自动变暗,帮助用户识别当前活跃窗口。

开发者可以通过 appearsActive 环境值精确控制自定义视图的非活跃状态表现:

struct SidebarFooterView: View {
    @Environment(\.appearsActive) private var appearsActive

    var body: some View {
        MyAccountView()
            .opacity(appearsActive ? 1 : 0.5)
    }
}

关键点:

  • appearsActive 是 2027 新增的环境值
  • 当窗口非活跃时返回 false,活跃时返回 true
  • 用于自定义侧边栏、工具栏等元素的视觉反馈

iPad 和 Mac 的菜单栏默认只显示关键操作的图标。通过 .labelStyle(.titleAndIcon) 可以让特定菜单项同时显示图标和文字,使其更醒目。(03:34

Toolbar 自适应与可见性控制

随着功能增加,Toolbar 的按钮越来越多。在 iPhone 或窗口较小时,系统会自动隐藏放不下的按钮。2027 版本提供了三个新 API 来解决这个问题。

可见性优先级:通过 visibilityPriority(.high) 让重要按钮在空间不足时优先保留。

StickerPageView()
    .toolbar {
        ToolbarItemGroup {
            UndoButton()
            RedoButton()
        }
        .visibilityPriority(.high)
        ToolbarOverflowMenu {
            ChoosePhotoButton()
            ExportAsImageButton()
            ClearAllStickersButton()
        }
        ToolbarItem(placement: .topBarPinnedTrailing) {
            ShareButton()
        }
    }

关键点:

  • visibilityPriority(.high) 标记 Undo/Redo 为重要操作,优先保留可见
  • ToolbarOverflowMenu 将不常用的按钮固定放入溢出菜单
  • .topBarPinnedTrailing 让 Share 按钮始终显示在右侧,不会被隐藏

自动最小化:当用户向下滚动时,导航栏自动收起,为内容留出更多空间。(07:37

ScrollView {
    StickerListView()
}
.toolbarMinimizeBehavior(.onScrollDown, for: .navigationBar)

全新的 Document API

SwiftUI 原有的 FileDocumentReferenceFileDocument 协议在 2027 版本上得到了大幅扩展。新 API 提供了文档创建上下文、磁盘读写性能优化,以及直接访问文档 URL 的能力。(08:12

文档创建源:应用启动时可以提供多种创建文档的方式。

@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)
        }
    }
}

extension DocumentCreationSource {
    static let blank = Self(id: "blank")
    static let photo = Self(id: "photo")
}

关键点:

  • DocumentGroupLaunchScene 定义启动场景中的新建文档按钮
  • DocumentCreationSource 声明不同的创建来源(空白、从照片等)
  • 创建闭包通过 context 参数接收来源信息,初始化器据此决定行为

写入优化:新协议 WritableDocument 要求三个部分:可写入格式列表、快照方法、Writer。(11:25

@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)
    }
}

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 {
        // 对比 current 和 previous snapshot,只写入变化的部分
        // 使用 Subprogress 报告写入进度
    }
}

关键点:

  • snapshot() 返回文档某一时刻的完整状态,用 sending 保证线程安全
  • Writerwrite 方法是 nonisolated 且异步的,磁盘写入在后台执行
  • 通过对比 snapshotprevious 实现增量写入
  • Subprogress 用于向系统报告保存进度

多格式导出:同一个 Writer 可以支持多种输出格式。(14:35

@Observable
final class StickerDocument: WritableDocument {
    static let writableContentTypes: [UTType] = [.stickerDocument, .png]
}

// 在 write 方法中根据 contentType 分发
if contentType.conforms(to: .stickerDocument) {
    // 写入自定义包格式
} else if contentType.conforms(to: .png) {
    let context = CGContext(/* ... */)
    context.draw(/* ... */)
}

读取协议ReadableDocumentWritableDocument 对称,配合 DocumentReader 完成磁盘读取。(13:27

可重排序容器

2027 版本引入了 Reorderable API,支持在 List、Grid 等任意容器中拖拽重排。(15:58

List {
    ForEach(stickers) { sticker in
        StickerListItemView(sticker: sticker)
    }
    .reorderable()
}
.reorderContainer(for: Sticker.self) { difference in
    difference.apply(to: &stickers)
}

同样的代码可以直接用于 LazyVGrid,无需修改重排逻辑:

LazyVGrid {
    ForEach(stickers) { sticker in
        StickerListItemView(sticker: sticker)
    }
    .reorderable()
}
.reorderContainer(for: Sticker.self) { difference in
    difference.apply(to: &stickers)
}

关键点:

  • .reorderable() 修饰 ForEach,启用拖拽交互
  • .reorderContainer(for:) 修饰外层容器,协调重排手势
  • difference.apply 使用 swift-collections 的 OrderedDictionary 提交排序变更
  • 首次支持 watchOS

任意视图的滑动手势

滑动手势不再仅限于 List,现在可以应用到任何视图。(18:15

ScrollView {
    LazyVStack {
        ForEach(stickers) { sticker in
            StickerListItemView(sticker: sticker)
                .swipeActions {
                    DeleteButton(sticker: sticker)
                }
        }
    }
}
.swipeActionsContainer()

关键点:

  • .swipeActions() 修饰单个列表项
  • .swipeActionsContainer() 修饰外层 ScrollView,协调所有子项的滑动手势

Confirmation Dialog 与 Alert 的 item 绑定

confirmationDialogalert 现在支持与 sheet 相同的 item 绑定模式。(18:54

struct StickerCanvasView: View {
    var stickers: [Sticker]
    @State private var stickerToDelete: Sticker?

    var body: some View {
        ZStack {
            ForEach(stickers) { sticker in
                PlacedStickerView(sticker: sticker)
                    .contextMenu {
                        Button("Delete") { stickerToDelete = sticker }
                    }
            }
        }
        .confirmationDialog("Delete?", item: $stickerToDelete) { sticker in
            DeleteStickerButton(sticker)
        }
    }
}

关键点:

  • item: $stickerToDelete 绑定一个可选值
  • 当值非 nil 时弹窗自动出现,nil 时自动消失
  • alert 也支持相同的 item 绑定模式

详细内容

AsyncImage 缓存改进

以前 AsyncImage 不会在内存中保留已加载的图片。向上滚动后再向下,图片会重新加载。2027 版本默认启用了标准 HTTP 缓存,无需修改代码。(20:57

如果需要更精细的控制,可以传入自定义的 URLRequestURLSession

@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)

关键点:

  • URLRequest 允许设置缓存策略、请求头等
  • asyncImageURLSession 修饰符设置全局的 URLSession,适合长期配置
  • 默认缓存自动生效,无需额外代码

@State 宏化与惰性初始化

@State 在 2027 版本从属性包装器变成了宏(macro)。最大的变化是:@State 修饰的 Observable 类现在只会初始化一次。(23:08

@Observable class StickerStore { }

struct StickerStoreView: View {
    @State private var store = StickerStore()

    var body: some View {
        // store 只在首次初始化时创建,父视图更新不会重复创建
    }
}

这个行为已回溯到 iOS 17、macOS 14 及对应版本。

迁移注意:如果 @State 变量有默认值,又在 init 中赋值,会产生编译错误。(23:48

// 错误:Variable 'self.title' used before being initialized
struct StickerPageView: View {
    @State private var page = StickerPage()
    let title: String

    init(title: String) {
        self.page = StickerPage(title: title)
        self.title = title
    }
}

// 修正:去掉默认值
struct StickerPageView: View {
    @State private var page: StickerPage
    let title: String

    init(title: String) {
        self.page = StickerPage(title: title)
        self.title = title
    }
}

ContentBuilder 编译性能优化

深层嵌套的 SwiftUI 视图常常触发编译器错误:“The compiler is unable to type-check this expression in reasonable time”。(24:31

原因是 SectionGroupForEach 等容器各有多个初始化器重载,编译器需要尝试所有组合才能确定类型。2027 版本将这些常用 builder 统一为 ContentBuilder,消除了大部分类型检查分支。(26:07

@ContentBuilder
func stickerLibraryView() -> some View {
    // ...
}

关键点:

  • ContentBuilderViewBuilder 的演进,统一了多种 builder 类型
  • 编译性能提升适用于所有最低部署目标
  • 使用 Xcode 27 构建时生效,无论目标平台是 2027 还是更早版本

核心启发

1. 用新 Document API 重构现有文档应用

如果你维护一个基于 FileDocument 的应用,迁移到新的 WritableDocument/ReadableDocument 协议可以获得增量写入、异步保存进度、多格式导出等能力。入口是 DocumentGroupDocumentGroupLaunchScene

2. 为 iPad/Mac 应用添加 Toolbar 自适应

visibilityPriority(.high) 标记核心操作,用 ToolbarOverflowMenu 收纳次要按钮,用 .topBarPinnedTrailing 固定分享按钮。配合 .toolbarMinimizeBehavior(.onScrollDown) 在滚动时自动收起导航栏,提升内容阅读体验。

3. 用 Reorderable API 替代自定义拖拽排序

如果你之前用 onMove 或自定义 DragGesture 实现列表重排,现在可以用 .reorderable() + .reorderContainer() 替换。代码更简洁,自动获得动画和交互,且支持 List、Grid、LazyVGrid 等多种容器。

4. 为任意滚动视图添加滑动手势

不再局限于 ListLazyVStackScrollView 中的任意子视图都可以添加 .swipeActions()。只需在外层容器加 .swipeActionsContainer() 即可协调手势。

5. 检查 @State 宏化带来的编译错误

升级到 Xcode 27 后,检查项目中 @State 变量是否在 init 中重复赋值。修复方式是去掉属性声明处的默认值,只在 init 中赋值。

关联 Session

评论

GitHub Issues · utterances