← 所有文章

iOS 27 的 SwiftUI 有哪些新功能

每一次 SwiftUI 的釋出,都能從 Apple 決定重建哪些部分,看出這套框架的壓力點落在何處。iOS 27 的答案出奇地廣泛:清單獲得了一流的重新排序能力,文件取得了一組全新的讀取/寫入協定家族,工具列有了帶明確優先順序的溢位模型,而錯誤呈現終於有了一個可以直接交給它一個 Error 的繫結。這次釋出一口氣推動了四個介面,多數應用程式至少會碰到其中兩個。1

面對如此廣泛的一次釋出,誘惑在於想全部採用。更明智的做法,是辨別哪些新增功能會改變你的建構方式,哪些只是針對你已在使用的模式所提供的便利多載。拖曳重新排序與文件協定屬於前者:它們取代了你手寫的程式碼。以項目為基礎的警示與 AsyncImage(request:) 則屬於後者:它們移除了一道權宜之計。本文將 iOS 27 的 SwiftUI 介面整理成這幾條脈絡,附上真實的宣告,以及每一項何時值得寫進你的程式碼的理由。

Watch on Apple Developer ↗
Apple 的 UI 框架團隊將 iOS 27 的 SwiftUI 釋出,圍繞著四個同時發生的壓力點來陳述:精煉的外觀與質感、文件 API、新的互動方式,以及效能。

在 269 場次中,Apple 將這次釋出描繪為四條同時推進的廣泛脈絡——精煉的外觀與質感、強大的全新文件 API、新的互動方式,以及效能工作——而非單一的招牌功能。35

TL;DR/重點摘要

  • 清單與自訂容器獲得了宣告式的重新排序:reorderContainer(for:isEnabled:move:) 標記一個容器,reorderable() 套用在 DynamicViewContent 上讓那些列加入其中,而你收到的是一個 ReorderDifference,無須再手寫索引運算。23
  • 延遲式拖曳容器透過 dragContainer(for:itemID:in:_:) 搭配 draggable(containerItemID:containerNamespace:) 登場,後者只攜帶一個識別碼,因此框架會在拖曳開始時才延遲取得負載。45
  • 全新的文件模型以 ReadableDocumentWritableDocument 的形式落地(由 DocumentReaderDocumentWriter 負責磁碟上的工作,而 FileWrapperDocumentReaderFileWrapperDocumentWriter 則處理簡單的情況),並以 URLDocumentConfiguration 為後盾。6789101112
  • 工具列獲得了 ToolbarOverflowMenutopBarPinnedTrailing 擺放位置,以及 visibilityPriority(_:),因此當工具列空間不足時,由你決定哪些控制項得以保留。131415
  • 錯誤呈現獲得了 alert(error:actions:message:) 與以項目為基礎的 alert(_:item:actions:)confirmationDialog,另外還有 AsyncImage(request:) 提供完整的 URLRequest 控制權,以及 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 為鍵,因此一個從某個段落跨進另一個段落的移動是可以表達的。Apple 的指引很直接:當你的容器有多個集合時,使用多集合多載;當它只有一個時,使用單一集合的便利版本。27 當你需要消除歧義時,reorderable() 修飾符會接受相對應的集合識別碼。3

Watch on Apple Developer ↗
Apple 透過在一個 ForEach 上加入 reorderable()、並在其父層加入一個 reorderContainer,來啟用重新排序,並在閉包中處理那份差異。

在 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,各自由一個檔案包裝器(file wrapper)支撐,並被 Apple 描述為簡單情況下的高效選擇:1011

struct FileWrapperDocumentReader<Snapshot>
struct FileWrapperDocumentWriter<Snapshot>

把開啟的文件繫在一起的是 URLDocumentConfiguration,一個持有開啟文件之設定與屬性的 main-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 時出現,isPresentedonCompletion 執行之前被設為 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,因此同一套由項目驅動的模式貫穿了警示與對話框。183031 Apple 的契約在每一處都相同:資料必須非 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
)

還有一個 contentplaceholder 形式,用於常見的「在載入完成前顯示這個、成功時顯示那個」的拆分。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 淡入覆蓋在內容之上,而非向上移動以蓋住內容。23 TabRole.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 統一了 Result Builders

上述的新增功能都是 API 介面。2027 週期中有一項變更屬於管路,它觸及你所編譯的每一個視圖,而非你所採用的任何單一視圖。269 場次透過一個多數 SwiftUI 開發者都遇過的錯誤來陳述它:「編譯器無法在合理時間內對此運算式進行型別檢查。」37

成因在於多載解析。一個把內容包進 SectionGroupForEach 的視圖,會迫使編譯器走一棵決策樹。如同 Apple 的解釋,「首先編譯器必須選擇要使用 Section 的哪一個多載。Section 可以用一個產生 View 或 TableRowContent 的建構器來初始化。為了知道該用哪一個,編譯器必須兩個選項都試。」這種分支會層層巢狀:「對於巢狀的 ForEach,編譯器必須逐一嘗試。接著,ForEach 的建構器又有它自己一組需要被檢查的選項。」每一層都讓路徑倍增,而「逐一嘗試這些路徑會讓型別檢查的代價愈來愈高。」37

這項修正讓這棵樹塌縮。「最常見的那組建構器如今共用單一的初始化器,只留下一條直截了當的路徑。這之所以可能,是因為多種不同的建構器型別已被統一在單一建構器之下:ContentBuilder!」37 Apple 把它定位為一段更長弧線的起點:「這是朝著讓 SwiftUI 所有 API 都採用統一建構器而邁出的一步。」37

有兩項特性讓 ContentBuilder 現在就值得依賴,而非留待日後。它不帶任何部署目標成本:「ContentBuilder 可以用於任何最低部署目標,因為在底層,它是既有 ViewBuilder 的一次演進。」37 而這份收穫不論你交付什麼,都在建構時兌現:「ContentBuilder 在使用 Xcode 27 建構時,為 SwiftUI 的型別檢查效能帶來了大幅改善;無論你的目標是 2027 的釋出,還是先前的釋出皆然。」37 Apple 的文件在其宣告中證實了這份向後相容性: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. 審慎地遷移文件應用程式,而非反射性地。 ReadableDocumentWritableDocument 的拆分是正確的模型,但它是比其他幾項更大的一次變更;當你本來就在動文件層時再採用它,並在小型與中型的情況下倚靠 FileWrapperDocumentReaderFileWrapperDocumentWriter,而非親手實作讀取器與寫入器協定。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,由 DocumentReaderDocumentWriter 負責磁碟上的工作,並有一個 Document 型別別名代表一個同時可讀又可寫的型別。67 對於不需要自訂邏輯的小型與中型文件,FileWrapperDocumentReaderFileWrapperDocumentWriter 提供了實作;URLDocumentConfiguration 則描述一份開啟的文件。101112 這道拆分讓一個僅供檢視的功能得以只遵循讀取端。

在 iOS 27 中,我現在可以直接從一個 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 的底層基質(result builders、不透明型別、值型別的視圖樹);本文 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:Observation 與 History

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 分鐘閱讀