← 所有文章

iOS 27 中 SwiftUI 的新变化

每一次 SwiftUI 发布,都会通过 Apple 选择重建哪些部分,告诉你这个框架的压力点曾经在哪里。iOS 27 给出的答案格外广泛:列表获得了一流的重排序能力,文档获得了一组新的读取/写入协议族,工具栏获得了带有明确优先级的溢出模型,而错误呈现终于得到了一个可以直接接收 Error 的绑定。这次发布一次性推动了四个层面,而大多数应用至少会触及其中两个。1

面对如此广泛的一次发布,诱惑在于把所有东西都用上。更明智的做法,是辨别哪些新增项改变了你的构建方式,哪些只是对你已经在用的模式的便捷重载。拖拽重排序和文档协议属于前者:它们取代了你手写的代码。基于条目的弹窗和 AsyncImage(request:) 属于后者:它们消除了一种变通手法。本文将 iOS 27 的 SwiftUI 层面梳理成这几条脉络,附上真实的声明,以及每一项何时值得写进你代码里的理由。

Watch on Apple Developer ↗
Apple 的 UI Frameworks 团队将 iOS 27 的 SwiftUI 发布围绕同时存在的四个压力点来组织:精炼的外观与质感、文档 API、新的交互方式,以及性能。

在 session 269 中,Apple 将这次发布定位为四条同时推进的宽线索:精炼的外观与质感、强大的全新文档 API、新的交互方式,以及性能工作,而非某一个标志性的单一特性。35

TL;DR / 要点速览

  • 列表和自定义容器获得了声明式的重排序能力:reorderContainer(for:isEnabled:move:) 标记一个容器,DynamicViewContent 上的 reorderable() 让行加入进来,你收到的是一个 ReorderDifference,而不必再手写索引运算。23
  • 惰性拖拽容器通过 dragContainer(for:itemID:in:_:) 加上 draggable(containerItemID:containerNamespace:) 到来,后者只携带一个标识符,于是框架会在拖拽开始时惰性地获取载荷。45
  • 全新的文档模型以 ReadableDocumentWritableDocument 的形式落地(由 DocumentReader/DocumentWriter 完成磁盘上的工作,简单场景则交给 FileWrapperDocumentReader/FileWrapperDocumentWriter),并由 URLDocumentConfiguration 支撑。6789101112
  • 工具栏获得了 ToolbarOverflowMenutopBarPinnedTrailing 放置位置以及 visibilityPriority(_:),于是当工具栏空间不足时,由你决定哪些控件得以保留。131415
  • 错误呈现获得了 alert(error:actions:message:) 以及基于条目的 alert(_:item:actions:) / confirmationDialog,外加用于完整控制 URLRequestAsyncImage(request:) 和用于共享会话的 asyncImageURLSession(_:)1617181920
  • 补足整个层面的还有:swipeActions(...onPresentationChanged:)swipeActionsContainer()NavigationTransition.crossFadeTabRole.prominentUIHostingSceneDelegate 以及 GestureInputKinds212223242526

拖拽重排序变为声明式

在 SwiftUI 中重排序一个列表,过去意味着 onMove、一个索引集合,以及一个你要转换为自身模型变更的目标偏移量。iOS 27 用一个两段式声明取而代之:你把容器标记为可重排序,再把它的内容标记为参与重排序。随后容器会交给你一份结构化的差异。这份差异所变更的模型,在 iOS 27 中本身也被更好地观察着:SwiftData 在同一次发布中获得了一流的观察与持久化历史 API,于是你在一个视图中重排序的存储,在它被读取的每一处都保持同步。

单集合的情形最为常见。你在容器上声明 reorderContainer(for:isEnabled:move:),并在其内部的 DynamicViewContent 上声明 reorderable()23

struct LandmarkList: View {
    @State private var landmarks: [Landmark]

    var body: some View {
        List {
            ForEach(landmarks) { landmark in
                LandmarkRow(landmark: landmark)
            }
            .reorderable()
        }
        .reorderContainer(for: Landmark.self) { difference in
            // Apply the reorder to your model.
            landmarks.apply(difference)
        }
    }
}

签名说明了契约。容器修饰符对 Item : Identifiable 泛型化,并交给你一个 ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>2

nonisolated func reorderContainer<Item>(
    for item: Item.Type,
    isEnabled: Bool = true,
    move: @escaping (ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>) -> ()
) -> some View where Item : Identifiable, Item.ID : Sendable

这里有两个设计决策值得关注。其一,由框架来驱动交互:正如 Apple 文档所述,一个可重排序的条目可以通过拖拽手势被提起,一个占位视图占据它的位置以显示该条目将落在何处,并且占位视图会随着拖拽在容器内移动。2你不再去构建这种交互效果;你只描述集合,并对结果做出反应。其二,isEnabled 是一个参数而非单独的修饰符,于是一个编辑模式的开关就变成了一个布尔值,而不是有条件的视图构建。

当一个容器持有多个集合时,你要使用接收集合标识符类型的重载 reorderContainer(for:in:isEnabled:move:)27

nonisolated func reorderContainer<Item, CollectionID>(
    for item: Item.Type,
    in collectionID: CollectionID.Type,
    isEnabled: Bool = true,
    move: @escaping (ReorderDifference<Item.ID, CollectionID>) -> ()
) -> some View where Item : Identifiable, CollectionID : Hashable, CollectionID : Sendable, Item.ID : Sendable

此时的 ReorderDifference 同时以条目 ID 和集合 ID 为键,于是一次从某个 Section 跨越到另一个的移动就可以被表达出来。Apple 的指引很直接:当你的容器有多个集合时使用多集合重载,只有一个集合时则使用单集合便捷方法。27当你需要消除歧义时,reorderable() 修饰符会接收相匹配的集合标识符。3

Watch on Apple Developer ↗
Apple 通过给 ForEach 添加 reorderable()、给它的父级添加 reorderContainer,并在闭包中处理差异,来启用重排序。

在 session 271 中,Apple 展示了同一段重排序代码原封不动地从 List 迁移到 LazyVGrid,因为这些修饰符描述的是集合而非容器,于是重排序在任何支持拖放的容器中都能工作。36

惰性拖拽容器

iOS 27 拖拽叙事的另一半,关乎开销。经典的 draggable 要求你预先生成载荷,这意味着框架可能需要在拖拽开始之前就实例化一个条目(有时还要渲染它)。对于一个有数千行的惰性列表来说,那是被浪费掉的工作。

dragContainer(for:itemID:in:_:) 定义了一个由可拖拽视图组成的容器,并且只通过一个针对被拖拽标识符的闭包,按需索取一次载荷:4

nonisolated func dragContainer<ItemID, Item, Data>(
    for itemType: Item.Type = Item.self,
    itemID: KeyPath<Item, ItemID>,
    in namespace: Namespace.ID? = nil,
    _ payload: @escaping (Array<ItemID>) -> Data
) -> some View where ItemID : Hashable, ItemID : Sendable, Item : Transferable, Item == Data.Element, Data : Collection

在该容器内部,每个可拖拽的子视图使用 draggable(containerItemID:containerNamespace:),它只携带条目的标识符:5

nonisolated func draggable<ItemID>(
    containerItemID: ItemID,
    containerNamespace: Namespace.ID? = nil
) -> some View where ItemID : Hashable, ItemID : Sendable

它之所以对大型集合更适合作为默认选择,原因就在 Apple 自己的描述里:因为这个修饰符只提供标识符而非载荷,所以它是惰性工作的,于是框架仅在拖拽开始时才索取实际被拖拽的条目,并且无需为了访问载荷而渲染一个视图。5一个以名称标识自身(且从不遵从 Identifiable)的 Fruit 值,仍然可以作为多条目拖拽的来源,因为容器是以你提供的 KeyPath 为键,而非以 Identifiable 的遵从为键。4

对于跨平台代码,有一点值得标注:dragContainerdraggable(containerItemID:containerNamespace:) 在 macOS 26.0 上就已可用,而这次发布的其余大部分内容是 macOS 27.0,所以这个惰性拖拽 API 是你本可以在 Mac 上提前采用的。45

全新的文档模型

DocumentGroupFileDocument 多年来一直承载着 SwiftUI 的文档类应用,但读取端与写入端纠缠在单一的遵从之中。iOS 27 把它们拆开了。读取和写入如今是各自独立的协议,磁盘上的逻辑成了它自己的一层,而一个可读写的类型则把两者组合起来。

两个顶层协议是 ReadableDocumentWritableDocument67

protocol ReadableDocument : AnyObject
protocol WritableDocument : AnyObject

一个只读类型仅遵从 ReadableDocument。一个可读写类型同时遵从两者,而 Apple 提供了一个 Document 类型别名将二者捆绑起来,于是你无需逐一指明就能采用这一对。67两者都受类约束(: AnyObject),这是一个可见的信号,表明在该模型中文档是引用类型。

实际的磁盘 I/O 移入了一个读取器和一个写入器抽象 DocumentReaderDocumentWriter,二者各自以你的文档所序列化的快照类型为参数:89

protocol DocumentReader<Snapshot>
protocol DocumentWriter<Snapshot>

大多数应用永远不会手写这些。对于无需自定义逻辑的中小型文档,SwiftUI 提供了 FileWrapperDocumentReaderFileWrapperDocumentWriter,二者各自由一个文件包装器支撑,并被 Apple 描述为简单场景下的高效选择:1011

struct FileWrapperDocumentReader<Snapshot>
struct FileWrapperDocumentWriter<Snapshot>

把打开的文档串联起来的是 URLDocumentConfiguration,这是一个主 actor 类,持有一个打开文档的设置与属性:12

@MainActor final class URLDocumentConfiguration

导出通过一个更新后的 fileExporter 进行,它接收一个其写入器以 URL 为目标的 WritableDocument28

nonisolated func fileExporter<D>(
    isPresented: Binding<Bool>,
    document: D?,
    contentType: UTType? = nil,
    defaultFilename: String? = nil,
    onCompletion: @escaping (Result<URL, any Error>) -> Void,
    onCancellation: (() -> Void)? = nil
) -> some View where D : WritableDocument, D.Writer.Destination == URL

约束 D.Writer.Destination == URL 是承重的部分:导出器只接受一个其写入器写往 URL 的可写文档,而这正是系统对话框所处理的磁盘文件场景。Apple 对其生命周期有精确的文档说明:仅当 document 非 nil 时对话框才出现,isPresented 会在 onCompletion 运行之前被设为 false,而用户取消则会把 isPresented 设为 false 并调用 onCancellation28可读与可写之间的拆分,正是让一个纯查看功能得以导入文档而永不遵从写入端的原因。

由工具栏决定哪些得以保留

工具栏会空间不够。一部紧凑宽度的 iPhone、一个被调整过大小的 Mac 窗口,或者一个激活的搜索框,都可能让可用的槽位少于你拥有的控件数量。在 iOS 27 之前,由框架替你做出驱逐决策。如今由你来做。

ToolbarOverflowMenu 是显式的溢出界面。Apple 将其描述为:无论工具栏模式、平台或可自定义性如何,都始终被放入工具栏溢出菜单的操作;而在 iOS 和 visionOS 上,这些内容会落入导航栏的溢出菜单中:13

nonisolated struct ToolbarOverflowMenu<Content> where Content : View

对于那些应当抵抗溢出的控件,新的 topBarPinnedTrailing 放置位置会把一个条目固定到工具栏的尾随边缘:14

static let topBarPinnedTrailing: ToolbarItemPlacement

Apple 文档说明的微妙之处在于:被固定的条目只有在搜索激活且空间不足时才会移入溢出菜单,而在 iOS 和 visionOS 上顶部栏就是导航栏。14所以 topBarPinnedTrailing 适用于那一两个你绝不想被埋没的控件,除非搜索逼得你不得不为之。

当选择是相对而非绝对时,ToolbarContent 上的 visibilityPriority(_:) 会对条目排序,让框架知道驱逐的次序:15

@MainActor @preconcurrency
func visibilityPriority(_ priority: ToolbarItemVisibilityPriority) -> some ToolbarContent

Apple 的规则是:当工具栏空间受限时,优先级较低的条目会先于优先级较高的条目移入溢出菜单。15一个重要的控件可以坐落在尾随边缘,又在窗口收缩时仍被显示。与 topBarPinnedTrailingToolbarOverflowMenu 搭配,你如今拥有了一套完整的优雅降级词汇:固定必不可少的,对其余的排定优先级,并把那些始终次要的引导进溢出。

有一个相关的修饰符把工具栏与滚动行为、以及 iOS 26 引入的 Liquid Glass 外壳联系起来。toolbarMinimizeBehavior(_:for:) 启用响应滚动的工具栏最小化,而 Apple 指出:当导航栏最小化时,一个集成的顶部标签栏会随之一同最小化:29

nonisolated func toolbarMinimizeBehavior(
    _ behavior: ToolbarMinimizeBehavior,
    for bars: ToolbarPlacement...
) -> some View

受支持的放置位置是导航栏,并且默认情况下安全区域会随着栏的最小化而调整。29如果你采用了 Liquid Glass 工具栏,并希望它们在用户阅读时退隐,那么这就是实现它的修饰符。

基于条目的弹窗与错误呈现

SwiftUI 的弹窗 API 长期以来有一个布尔形式(alert(_:isPresented:)),它迫使你把弹窗的数据连同呈现标志一起塞进一个单独的 @State。iOS 27 新增了 sheet 和 popover API 早已具备的基于条目的形式,于是数据本身就是触发器。

基于条目的弹窗在绑定非 nil 时呈现,并把解包后的值传入你的操作构建器:17

nonisolated func alert<A, T>(
    _ title: Text,
    item data: Binding<T?>,
    @ContentBuilder actions: (T) -> A
) -> some View where A : View

有一个相匹配的重载增加了消息构建器 alert(_:item:actions:message:),以及一对针对 confirmationDialog 的对等项,于是同样的条目驱动模式在弹窗和对话框之间得以贯通。183031Apple 在每一种里的契约都相同:数据必须非 nil 呈现才会出现,而在呈现发生之后你对数据所做的更改会被忽略。18

错误呈现的重载才是真正全新的能力。你不必把一个错误映射成自定义结构体,而是直接绑定一个 Error16

nonisolated func alert<E, A, M>(
    error: Binding<E?>,
    @ContentBuilder actions: (E) -> A,
    @ContentBuilder message: (E) -> M
) -> some View where E : Error, A : View, M : View

让它值得采用的是它的行为。当错误值非 nil 时,系统呈现弹窗,并且如果该错误是一个 LocalizedError,标题会从错误的 errorDescription 推断而来;否则标题回退为本地化描述。16一个你早已定义好的 LocalizedError,如今无需额外接线就能驱动它自己的弹窗标题。一个更简单的重载 alert(error:actions:),在你只需要一个 OK 操作时省去了消息构建器:19

nonisolated func alert<E, A>(
    error: Binding<E?>,
    @ContentBuilder actions: () -> A
) -> some View where E : Error, A : View
struct EditorView: View {
    @State private var saveError: SaveError?

    var body: some View {
        Form { /* ... */ }
            .alert(error: $saveError) { error in
                Button("Retry") { retry() }
                Button("Cancel", role: .cancel) { }
            } message: { error in
                Text(error.recoverySuggestion ?? "")
            }
    }
}

随之消失的那个模式:一个手工编写的 AlertError 结构体、一个 identifiable 包装器,以及你真实错误类型与弹窗数据源之间的映射代码。你绑定的是你代码本就产生的那个 Error?

AsyncImage 随 URLRequest 走向成熟

AsyncImage 起初带着一个 URL 初始化器,却无法设置请求头、缓存策略或超时时间。iOS 27 的新增项接收一个 URLRequest,而它正是同时承载这三者的对象。

最简单的形式从一个请求中加载并显示图像:20

nonisolated init(request: URLRequest, scale: CGFloat = 1) where Content == Image

分阶段的形式交给你 AsyncImagePhase 以驱动一个内容闭包,而 Apple 指出你可以通过请求来指定缓存策略和超时时间间隔:32

nonisolated init(
    request: URLRequest?,
    scale: CGFloat = 1,
    transaction: Transaction = Transaction(),
    @ContentBuilder content: @escaping (AsyncImagePhase) -> Content
)

还有一个 content/placeholder 形式,用于常见的“加载完成前显示这个,成功后显示那个”的分工。33这三者贯通的行为都是文档化的 AsyncImage 契约:SwiftUI 在加载完成前显示一个占位视图,成功时换入图像,失败时则保留占位视图。20

配套的修饰符是 asyncImageURLSession(_:),它把一个 URLSession 交给视图内部的各个 AsyncImage 实例,让它们用以获取数据:34

nonisolated func asyncImageURLSession(_ urlSession: URLSession) -> some View
var body: some View {
    List(avatars) { avatar in
        AsyncImage(request: URLRequest(url: avatar.url))
            .frame(width: 44, height: 44)
    }
    .asyncImageURLSession(authenticatedSession)
}

这一组合正是带鉴权图像加载的答案。一个请求让你能附加 Authorization 请求头或一个自定义缓存策略;会话修饰符则让整个子树共享一个已配置好的 URLSession(自定义请求头、磁盘缓存、代理),而不是让每个 AsyncImage 都回退到共享会话。对于一个要加载令牌后方可访问的头像的应用来说,那就是能用与不能用之间的差别。

同样落地的内容

还有几项较小的新增,每一项都值得用一行来讲,因为它们各自消除了一种特定的摩擦。

swipeActions 获得了一个带 onPresentationChanged: 闭包的重载,当某一行的滑动操作变为可见时该闭包以 true 触发,当它们被消除时以 false 触发,于是你可以在操作显示期间使该行变暗或更新周围的外壳。21对于建立在 ScrollViewLazyVStack 之上而非 List 的自定义行布局,swipeActionsContainer() 会像 List 早已自动做到的那样,协调各行之间的消除与互斥(把它应用于 List 则为无操作)。22

NavigationTransition.crossFade 是一种在出现视图与消失视图之间交叉淡入淡出的过渡;指定在一个 sheet 上时,它会让该 sheet 在内容之上淡入,而不是向上移动去覆盖内容。23TabRole.prominent 在受支持的标签栏中给某一个标签突出的视觉处理,而 Apple 指出:在没有显式 .prominent 标签的情况下,一个 .search 角色的标签可能会默认获得这种突出处理。24

UIHostingSceneDelegate 扩展了 UISceneDelegate 以桥接 SwiftUI 场景,让 UIKit 能够激活一个在遵从类的静态 rootScene 属性中声明的 SwiftUI 场景。25(它是这里唯一一项在大多数平台上可追溯到 iOS 26.0 的内容,在 27.0 beta 中触及 tvOS。25)这套场景管道比看上去更重要,因为 iOS 27 还把 UIKit 基于场景的生命周期变成了硬性要求:一个用最新 SDK 构建却未采用它的应用会直接启动失败。而 GestureInputKinds 是一个选项集合,指定一个手势应当识别哪些输入种类,是那些要区分(比如说)触摸与指针的手势的基础。26

ContentBuilder 统一了结果构建器

上面这些新增项是 API 层面。2027 周期中有一项改动属于管道,它触及你编译的每一个视图,而非你采用的某一个。Session 269 通过一个大多数 SwiftUI 开发者都遇到过的错误来呈现它:“The compiler is unable to type-check this expression in reasonable time.”37

原因在于重载解析。一个把内容包裹进 SectionGroupForEach 的视图,迫使编译器走一棵决策树。正如 Apple 所解释的:“首先编译器必须选择使用 Section 的哪个重载。Section 可以用一个产生 View 或 TableRowContent 的构建器来初始化。为了知道该用哪一个,编译器必须把两种选项都试一遍。”这种分支会嵌套:“对于嵌套的 ForEach,编译器将不得不逐一尝试。而后,ForEach 的构建器也有它自己一组需要被检查的选项。”每一层都使路径数量倍增,而“尝试这些路径中的每一条,让类型检查变得越来越昂贵。”37

这一修复把那棵树折叠了。“最常见的那组构建器如今共享单一个初始化器,只留下一条直截了当的路径。这之所以可行,是因为多个不同的构建器类型被统一到了单一个构建器之下:ContentBuilder!”37Apple 把它定位为一条更长弧线的起点:“这是迈向在 SwiftUI 所有 API 之上启用统一构建器的一步。”37

有两个特性让 ContentBuilder 现在就值得依赖,而非以后。它不带来任何部署目标上的代价:“ContentBuilder 可以与任意最低部署目标一起使用,因为在底层,它是现有 ViewBuilder 的一次演进。”37而且无论你最终交付什么,这份收益都落在构建期:“当你使用 Xcode 27 构建时,ContentBuilder 为 SwiftUI 中的类型检查性能带来了实质性的改进;无论你的目标是 2027 系列发布,还是同样包括以往的发布。”37Apple 文档在其声明中确认了这种向后兼容性:ContentBuilder 是一个类型别名,其可用性一路列回到了 iOS 13.0 和 macOS 10.15,被描述为“一个自定义参数特性,它从闭包构造视图及其他内容类型。”38

Swift 工程经理 Holly Borla 在她的 WWDC26 收尾访谈中印证了编译器这一侧。这个错误“是编译器类型检查器中的一种回退”,她解释道,而团队收窄了它出现的范围:“今年我们花了很大力气去缓解这个错误在嵌套闭包和 SwiftUI 视图体中的出现,那是一个非常常见的会看到它的地方。”40她还补充说这项工作仍在公开进行:“还有更多工作要做,你可以通过 Open Source Swift 项目跟进。”40

SwiftUI 团队在一场小组实验室中为这一改动赋予了第二个维度。以下内容转述自一份对 WWDC 2026 SwiftUI Group Lab 的本地转录录音,团队描述说,旧的按类型分的构建器重载曾给他们自己的 API 层面设了上限:每一个他们想添加构建器的地方都会让类型检查变差,于是他们有所克制,结果让 ForEach 及类似项可用的位置比他们本希望的更少。39这次统一抬高了那个天花板。团队还指出,统一构建器如今可以在视图之外使用,于是你可以从自己的构建块、而不仅仅是视图,组装出类似 SwiftUI 的自定义 DSL。39编译器上的胜利是标题;设计上的余裕是更安静的那个结果。

采用优先级

如此广泛的一次发布,回报偏爱分诊。先去取用这些。

  1. 替换手写的重排序。如果你维护着 onMove 的索引运算,那么 reorderContainer(for:isEnabled:move:) 加上 reorderable() 是代码的净删减,也是更好的交互(占位效果是系统的,不是你的)。23对于列表密集的应用,重排序 API 在这次发布中分量最重。
  2. 采用错误绑定的弹窗。alert(error:actions:message:) 把自定义的错误包装结构体从每一个呈现失败的屏幕上移除,而你早已拥有的一个 LocalizedError 现在为它自己的弹窗加上标题。16投入低,可读性立竿见影。
  3. 把大型拖拽来源切换到惰性容器。任何超过几百行可拖拽行的列表都会从 dragContainer(for:itemID:in:_:) 加上 draggable(containerItemID:containerNamespace:) 中获益,因为框架不再去实例化它可能永远用不上的载荷。45
  4. 给你的工具栏一个优先级方案。如果你的工具栏曾在紧凑宽度下溢出,那么 visibilityPriority(_:)topBarPinnedTrailingToolbarOverflowMenu 让你来决定哪些得以保留,而不是接受框架的默认。131415
  5. 有意识地迁移文档类应用,而非条件反射地。ReadableDocument/WritableDocument 的拆分是正确的模型,但它是一项比其余更大的改动;当你本就在触及文档层时再采用它,并在中小型场景下倚重 FileWrapperDocumentReader/FileWrapperDocumentWriter,而不是手工实现读取器和写入器协议。671011

贯穿始终的脉络是:采用那些删除你所维护代码的新增项,推迟那些重构本就能用代码的新增项。

常见问题

在 iOS 27 中如何让一个 SwiftUI 列表可重排序?

在容器上声明 reorderContainer(for:isEnabled:move:),并对其内部的 DynamicViewContent(通常是一个 ForEach)应用 reorderable()。容器的 move 闭包会收到一个你应用到自身模型的 ReorderDifference;框架负责拖拽手势、提起,以及标记放置位置的占位视图。23当一个容器持有多个集合时,使用带集合标识符类型的 reorderContainer(for:in:isEnabled:move:) 重载。27

draggable(containerItemID:) 与旧的 draggable 有什么区别?

draggable(containerItemID:containerNamespace:) 只携带条目的标识符而非载荷,于是它在 dragContainer(for:itemID:in:_:) 内部惰性工作:框架仅在拖拽开始时才索取实际被拖拽的条目,并且无需渲染一个视图来读取其载荷。45这使它成为大型或惰性加载集合的正确选择——在那些场景下预先生成每一份载荷会是被浪费掉的工作。

全新的 SwiftUI 文档模型与 FileDocument 有何不同?

iOS 27 把读取和写入拆分成各自独立的协议 ReadableDocumentWritableDocument,由 DocumentReader/DocumentWriter 完成磁盘上的工作,并以一个 Document 类型别名表示既可读又可写的类型。67对于无需自定义逻辑的中小型文档,FileWrapperDocumentReaderFileWrapperDocumentWriter 提供了实现;URLDocumentConfiguration 描述一个打开的文档。101112这种拆分让一个纯查看功能得以只遵从读取端。

现在在 SwiftUI 中可以直接从一个 Error 弹出弹窗吗?

可以。alert(error:actions:message:)alert(error:actions:) 接收一个 Binding<E?>,其中 E : Error。当被绑定的错误非 nil 时系统呈现弹窗,而如果该错误遵从 LocalizedError,标题会从它的 errorDescription 推断而来;否则使用本地化描述。1619你不必再把错误包装进一个自定义的 identifiable 结构体。

空间紧张时,如何控制哪些工具栏条目消失?

在你的 ToolbarContent 上使用 visibilityPriority(_:) 来对条目排序:随着空间收缩,优先级较低的条目会先于优先级较高的移入溢出菜单。15使用 topBarPinnedTrailing 把一个控件固定到尾随边缘,于是它只在搜索激活且空间不足时才移入溢出,并用 ToolbarOverflowMenu 声明那些始终位于溢出菜单中的操作。1314

在 iOS 27 中,AsyncImage 能发送自定义请求头或设置缓存策略吗?

可以。AsyncImage(request:scale:) 系列接收一个 URLRequest,它承载请求头、缓存策略和超时时间间隔;Apple 指出你可以通过请求来指定缓存策略和超时。2032要在一个子树内的各个 AsyncImage 实例间共享一个已配置好的 URLSession(用于鉴权或自定义缓存),应用 asyncImageURLSession(_:)34

完整的 Apple Ecosystem 系列:SwiftUI 的底层构成(结果构建器、不透明类型、值类型的视图树);iOS 27 工具栏最小化行为所配合的 Liquid Glass 模式;驱动本文每一个视图之下状态层的 @Observable 内部机制;以及面向后台执行、同步与 Spotlight 的并行 iOS 27 中的 App Intents 层面。中枢位于 Apple Ecosystem 系列。要了解更广泛的 iOS 与 AI 智能体背景,参见 iOS 智能体开发指南

参考资料


  1. Apple Developer Documentation: SwiftUI. The framework reference covering views, lists, documents, toolbars, and the iOS 27 additions described here. 

  2. Apple Developer Documentation: reorderContainer(for:isEnabled:move:) (iOS 27.0 beta). Defines a container that allows its items to be reordered; the single-collection convenience that delivers a ReorderDifference to its move closure. 

  3. Apple Developer Documentation: reorderable() (iOS 27.0 beta). Enables the views of DynamicViewContent to be reordered when used within the scope of a reorder container. 

  4. Apple Developer Documentation: dragContainer(for:itemID:in:_:) (iOS 27.0 beta; macOS 26.0). A container with draggable views; takes a KeyPath to each item’s identifier and a payload closure over the dragged identifiers. 

  5. Apple Developer Documentation: draggable(containerItemID:containerNamespace:) (iOS 27.0 beta; macOS 26.0). Activates a view as a drag source inside a drag container, supplying only an identifier so the container works lazily. 

  6. Apple Developer Documentation: ReadableDocument (iOS 27.0 beta). “A type that you use to read documents from file.” Declared as protocol ReadableDocument : AnyObject; for read-write, also conform to WritableDocument or use the Document typealias. 

  7. Apple Developer Documentation: WritableDocument (iOS 27.0 beta). “A type that you use to write documents to file.” Declared as protocol WritableDocument : AnyObject; conform alongside ReadableDocument to support saving. 

  8. Apple Developer Documentation: DocumentReader (iOS 27.0 beta). “Implements logic of reading documents from disk.” Declared as protocol DocumentReader<Snapshot>

  9. Apple Developer Documentation: DocumentWriter (iOS 27.0 beta). “Implements logic of writing documents to disk.” Declared as protocol DocumentWriter<Snapshot>

  10. Apple Developer Documentation: FileWrapperDocumentReader (iOS 27.0 beta). A document reader backed by a file wrapper; efficient for documents of small and medium size that need no custom reading logic. 

  11. Apple Developer Documentation: FileWrapperDocumentWriter (iOS 27.0 beta). A document writer backed by a file wrapper; efficient for documents of small and medium size that need no custom writing logic. 

  12. Apple Developer Documentation: URLDocumentConfiguration (iOS 27.0 beta). “A set of settings and properties of an open document.” Declared as @MainActor final class URLDocumentConfiguration

  13. Apple Developer Documentation: ToolbarOverflowMenu (iOS 27.0 beta). “The overflow menu of a toolbar.” Declared as nonisolated struct ToolbarOverflowMenu<Content> where Content : View; on iOS and visionOS the content is placed in the navigation bar’s overflow menu. 

  14. Apple Developer Documentation: topBarPinnedTrailing (iOS 27.0 beta). “A placement that pins the item to the trailing edge of the toolbar.” Pinned items only move to the overflow menu when search is active and there isn’t enough room. 

  15. Apple Developer Documentation: visibilityPriority(_:) (iOS 27.0 beta). “Defines the visibility priority for a toolbar item.” When toolbar space is limited, lower-priority items move into the overflow menu before higher-priority items. 

  16. Apple Developer Documentation: alert(error:actions:message:) (iOS 27.0 beta). “Presents an alert with a message when an error is present.” The title is inferred from the error’s errorDescription if it is a LocalizedError; otherwise from the localized description. 

  17. Apple Developer Documentation: alert(_:item:actions:) (iOS 27.0 beta). “Presents an alert using the given data to produce the alert’s content and a text view as a title.” For the alert to appear, data must not be nil

  18. Apple Developer Documentation: alert(_:item:actions:message:) (iOS 27.0 beta). The item-based alert overload with a message builder; the data must be non-nil and changes after presentation are ignored. 

  19. Apple Developer Documentation: alert(error:actions:) (iOS 27.0 beta). “Presents an alert when an error is present.” The error-binding overload without a message builder. 

  20. Apple Developer Documentation: init(request:scale:) (iOS 27.0 beta). “Loads and displays an image from the specified URL load request.” Declared as init(request: URLRequest, scale: CGFloat = 1) where Content == Image; shows a placeholder until the load completes. 

  21. Apple Developer Documentation: swipeActions(edge:allowsFullSwipe:content:onPresentationChanged:) (iOS 27.0 beta). The closure is called with true when a row’s swipe actions become visible and false when they are dismissed. 

  22. Apple Developer Documentation: swipeActionsContainer() (iOS 27.0 beta). Coordinates swipe-action dismissal and mutual exclusion across rows in a ScrollView or similar container; applying it to a List is a no-op. 

  23. Apple Developer Documentation: crossFade (iOS 27.0 beta). “A navigation transition that cross-fades between the appearing view and the disappearing view.” Specified on a sheet, it fades in over the content rather than moving upward to cover it. 

  24. Apple Developer Documentation: prominent (iOS 27.0 beta). “The prominent role.” Provides prominent visual treatment to one tab in supported tab bars; with no explicit .prominent tab, a .search role tab may receive it by default. 

  25. Apple Developer Documentation: UIHostingSceneDelegate (iOS 26.0; tvOS 27.0 beta). “Extends UISceneDelegate to bridge SwiftUI scenes.” Declare SwiftUI scenes to activate from UIKit in the static rootScene property of the conforming class. 

  26. Apple Developer Documentation: GestureInputKinds (iOS 27.0 beta). “An option set that specifies which input kinds a gesture should recognize.” 

  27. Apple Developer Documentation: reorderContainer(for:in:isEnabled:move:) (iOS 27.0 beta). “Defines a container that allows its items to be reordered.” The multi-collection overload, keyed by a collection identifier type; use it when a container holds more than one collection. 

  28. Apple Developer Documentation: fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:onCancellation:) (iOS 27.0 beta). Presents a system dialog to export a WritableDocument whose writer’s destination is URL; the dialog appears only when document is non-nil. 

  29. Apple Developer Documentation: toolbarMinimizeBehavior(_:for:) (iOS 27.0 beta). “Sets the minimize behavior for the specified bars.” Enables toolbar minimization in response to scrolling; the supported placement is the navigation bar, and an integrated top tab bar minimizes with it. 

  30. Apple Developer Documentation: confirmationDialog(_:item:titleVisibility:actions:message:) (iOS 27.0 beta). Presents a confirmation dialog with a message using data to produce the dialog’s content and a text view for the message. 

  31. Apple Developer Documentation: confirmationDialog(_:item:titleVisibility:actions:) (iOS 27.0 beta). The item-based confirmation dialog without a message builder. 

  32. Apple Developer Documentation: init(request:scale:transaction:content:) (iOS 27.0 beta). “Loads and displays a modifiable image from the specified URL load request in phases.” You can specify the cache policy and timeout interval via the request. 

  33. Apple Developer Documentation: init(request:scale:content:placeholder:) (iOS 27.0 beta). “Loads and displays a modifiable image from the specified URL load request using a custom placeholder until the image loads.” 

  34. Apple Developer Documentation: asyncImageURLSession(_:) (iOS 27.0 beta). “A modifier that adds a URL session for asynchronous images contained in the view to use when fetching image data.” 

  35. Apple, WWDC26 session 269, “What’s new in SwiftUI.” developer.apple.com/videos/play/wwdc2026/269. The session frames the release around a refined look and feel, a new document API, new ways to interact, and performance improvements. 

  36. Apple, WWDC26 session 271, “Code-along: Build powerful drag and drop in SwiftUI.” developer.apple.com/videos/play/wwdc2026/271. The reorder code is shown moving unchanged between a List and a LazyVGrid, since the reorderable API works with any container that supports drag and drop. 

  37. Apple, WWDC26 session 269, “What’s new in SwiftUI.” developer.apple.com/videos/play/wwdc2026/269. Source for the “unable to type-check this expression in reasonable time” error, the Section/Group/ForEach overload decision tree, the unification of the common builders under ContentBuilder, the step toward unified builders across SwiftUI, any-minimum-deployment-target support as an evolution of ViewBuilder, and the Xcode 27 type-checking improvement across the 2027 and previous releases. 

  38. Apple Developer Documentation: ContentBuilder. “A custom parameter attribute that constructs views and other content types from closures.” Declared as a typealias, with availability listed back to iOS 13.0 and macOS 10.15. 

  39. Apple, WWDC26 session 8006, “SwiftUI Group Lab.” developer.apple.com/videos/play/wwdc2026/8006. Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab; Apple publishes no official captions for the labs. Source for the per-type builder overloads having capped the team’s own API surface (each addition worsened type-checking) and for the unified builder being usable outside views to enable SwiftUI-like custom DSLs. 

  40. Apple, WWDC26 session 400, “Dub Dub Daily: Day 5”, official transcript. Holly Borla, Swift engineering manager, in the closing interview with Jeff; source for the “fallback in the compiler’s type checker” characterization, the focus on nested closures and SwiftUI view bodies, and the ongoing Open Source Swift work. 

相关文章

iOS 27 的无障碍设计:阅读类 App 与自定义控件

iOS 27 针对阅读类 App 与自定义控件的无障碍设计:文本导航链接、causesPageTurn、UITextInput、adjustable 特征与直接触摸。

2 分钟阅读

iOS 27 中的 SwiftData:观察与历史记录

iOS 27 为 SwiftData 带来一等的变更观察能力:用 ResultsObserver 跟踪变更、用 HistoryObserver 观察持久化历史,并支持 codable 属性存储。

4 分钟阅读

The Robots Are Taking Exams in My Search Console

First-party GSC data: 91% of 3.8M impressions fail a human-query filter. Exam questions, pasted errors, and agent sweeps…

10 分钟阅读