SwiftUIUIKitiPhone Duo

iPhone Duo 的豎向欄

iPhone Duo 如何把導覽列、工具列和標籤列移到側邊,哪些容器符合條件,以及如何擺放面板和浮動按鈕。

2026年10月4日閱讀約 7 分鐘10 個來源

在 iPhone Duo 上,介面框架會移動。導航項、工具列操作和標籤可以離開螢幕的頂部和底部,共用側邊的一列,把高度還給內容。只要用 iOS 27.1 SDK 建置,就會這樣。你仍然用同樣的位置來聲明這些項,由 SwiftUI 和 UIKit 決定它們去哪裡。

本文講的就是這個決定,以及開發者發現系統幫不上忙的那些情況。整體佈局請從為 iPhone Duo 做設計開始;摺痕本身請看摺痕指南。

要點

  • 用 iOS 27.1 SDK 重新建置,並把各種欄放在 NavigationStack、NavigationSplitView、TabView 或它們對應的 UIKit 控制器裡。這些會移動,你自己搭的欄不會。
  • 側邊欄出現在外螢幕上,以及橫拿的展開手機上。展開並豎拿時,各種欄回到頂部和底部。
  • 符號按鈕會自己轉過去。文字和自定義檢視會保持橫向,除非你聲明 .axisBehavior(.verticalPreferred)。
  • 關閉側邊欄是按呈現單位來選擇的。做一次決定,對整個頁面生效。
  • 面板會自己適配,並且可以放在前側、尾側或居中。浮動按鈕是唯一還需要你手動擺放的部分。

只有系統管的欄才會移動

NavigationStack、NavigationSplitView 或 TabView 上的 toolbar 符合條件。UINavigationController 或 UITabBarController 符合條件。你自己建立並塞進檢視層級的 UIToolbar、UINavigationBar 或 UITabBar 則不符合,它們的項永遠不會被考慮放進側邊欄。Apple 的工程師在 9 月的小組實驗室裡也說了同樣的話:自定義標籤列不會自己跳過去,否則你就得自己重新實現那些尺寸規則。

這一列是共用的。導航、工具列操作和標籤共佔一條,而不是三條。它出現在橫向空間充裕、縱向空間緊張的地方:外螢幕,以及橫向的內螢幕。在豎向的內螢幕上,各種欄會回到熟悉的頂部和底部。使用者每次轉動展開的手機都會看到這種變化。順著它來設計,而不是和它對著幹。Apple 的首發合作夥伴已經這麼做了:下面是 Detail,再往下是 Slack。

展開的 iPhone Duo 上的 Detail App:左邊是影片,右邊是字幕文稿,App 的按鈕在右側邊緣排成一列
橫拿的展開手機上的 Detail。返回按鈕、App 的操作和狀態圖示共用右側邊緣的一列。圖片:Apple

Sarun W. 根據 Raise the Bar 講座補充了一個在多欄分欄檢視(NavigationSplitView 這類佈局,不是多工的分割畫面)中很容易忽略的限制:只有當欄所在的容器貼著螢幕邊緣時,欄才會移動。詳情欄會拿到豎向欄,其他欄仍然是橫向的,展開的檢查器也是如此,以免與詳情欄爭搶。如果某個按鈕“不肯”移動,在動用關閉選項之前,先看看它在哪一欄裡。

展開的 iPhone Duo 上的 Slack:左邊是私信側邊欄,右邊是一段對話
展開手機上的 Slack。側邊欄的按鈕沿頂部排成一行,旁邊對話的按鈕則沿側邊排列。圖片:Apple

這一列屬於硬體,而不屬於閱讀方向。在從右到左書寫的語言中,它仍然在裝置的同一側,由內容圍繞它來適配。在列內,從上到下保持 Apple 規定的順序:先是導航(比如“返回”或“關閉”),再是主要操作(比如“完成”),然後是其他所有項,按原來的分組排列。

橫向欄的高度固定,項的寬度靈活。豎向欄正好相反:寬度固定,項的高度靈活,所以它需要的是符號,而不是句子。

告訴系統哪些項可以轉向

標準的符號按鈕可以自己加入側邊欄。純文字項,以及系統無法調整大小的自定義檢視,會保持橫向。分段控制元件或太寬的文字按鈕,即使周圍所有東西都移走了,也會留在橫向欄裡。如果你已經通過 Label 或對應的 UIBarButtonItem 屬性給按鈕同時提供了標題和影像,那你已經完成了大半,系統會挑選合適的形式。

想要不同的結果時,就明確設定。

.toolbar {
    ToolbarItem(placement: .bottomBar) {
        Compass()
    }
    .axisBehavior(.verticalPreferred)

    ToolbarItem {
        SelectOrDoneButton()
    }
    .axisBehavior(.horizontalOnly)
}

.verticalPreferred 是讓自定義檢視加入的方式。.horizontalOnly 即使項帶有圖示也讓它保持橫向,這正適合那種會在符號和“完成”二字之間切換的控制元件。Khoa Pham 記下了同一場講座裡的第三個技巧:一個混合了圖示和數字的自定義檢視,可以使用 badge(),讓數字變成標記,圖示就能加入側邊欄。

在自定義檢視內部,讀取你最終落在了哪裡。

struct Compass: View {
    @Environment(\.toolbarVerticalEdge) private var edge

    var body: some View {
        let layout = edge == nil ? AnyLayout(HStackLayout()) : AnyLayout(VStackLayout())
        layout {
            Image(systemName: "location.north")
            Text("N")
        }
    }
}

toolbarVerticalEdge 的值為 leading、trailing 或 nil。nil 表示此刻各種欄是橫向的,所以要繪製橫向版本。UIKit 用 traitCollection.verticalBarEdge 提供同樣的資訊。如果你設定了 .horizontalOnly,即使環境裡有邊緣值,也要保持橫向繪製,因為這個項並不在側邊欄裡。

有幾個位置值得記住名字。cancellationAction 會停留在側邊欄頂部。在 UIKit 裡,這對應一個前側項組,並把 leftItemsSupplementBackButton 設為 false,這樣你的關閉按鈕會取代返回按鈕的位置,而不是挨著它。topBarPinnedTrailing 以及 UIKit 中的固定尾側組,能讓“分享”這類主要操作不被擠進溢位選單。visibilityPriority 決定側邊欄空間不夠時誰能留下。預設情況下,項會從下往上溢位。

當側邊欄空間不夠時

標籤和工具列項共用這一條,而空間常常不夠。SwiftUI 可以把工具列項擠進溢位選單,或者把標籤收成一個切換器。toolbarVerticalCompressionBehavior 決定先發生哪一種。

在 iOS 上,預設偏向標籤列:工具列項溢位,標籤保持可見。你可以用 .prefersTabBar 明確要求這樣,或者在操作才是核心、標籤可以收起時,用 .prefersToolbarItems 反過來。Natalia Panferova 關於工具列的文章在同一個編輯器上展示了兩種做法。要選擇具體哪些項能在擠壓中留下,就給它們設定 visibilityPriority。

如果你有自己的溢位操作,用 ToolbarOverflowMenu 把它們併入系統選單,而不是再畫一個選單。UIKit 的對應版本是 additionalOverflowItems。

關閉側邊欄

.toolbarVerticalBehavior(.disabled) 會把各種欄送回頂部和底部,並恢復橫向的狀態列。UIKit 的等價寫法是讓 preferredVerticalBarBehavior 返回 .disabled。

當側邊欄佔用了你確實需要的寬度時,就用它:影片播放器、相機、面板上唯一的一個關閉按鈕,或者像設計指南裡翁天信的相框那樣的佈局。Apple 的建議(Natalia 也重申了)是,大多數介面都保留豎向欄。使用者會在其他 App 裡習慣這一列,而固定不動的欄會讓你的 App 看起來像是沒跟上更新。

如果確實要關閉,就對整個呈現單位關閉,並且保持不變。這個偏好在視窗一級生效,或者在最近的呈現單位(比如一個面板)生效。在一個視窗內,SwiftUI 會從 NavigationStack 的頂層檢視、TabView 當前選中的標籤,或 NavigationSplitView 的尾側欄讀取它,所以在使用者推入新頁面時改變它,會讓介面框架在操作進行到一半時突然翻轉。Artem Novichkov 在模擬器裡也看到了同樣的現象:這個設定會一路上傳到視窗或最近的呈現單位,這就是為什麼 Artem 的示例把那個頁面以全螢幕方式呈現。

在面板內部使用這個修飾符,只會影響面板本身,下面的頁面可以保持預設。

面板

面板無需額外程式碼就能獲得同樣的適配,此外還多了一個位置選項。

在外螢幕上,面板的控制元件會移到側邊。在內螢幕上,居中的面板保持橫向工具列。當手機半折時,系統會把面板滑離摺痕。你不用盯著轉軸就能做到這一點。

iOS 27 新增的 presentationPlacement 可取 automatic、center、leading 或 trailing。相簿可以在使用者點按的縮圖那一側打開面板。位置也會改變工具列。Apple 關於準備 App 的文件說,在內螢幕上,居中和前側的面板使用橫向工具列,尾側的面板使用豎向工具列。

在 iOS 27.1 測試版中,Natalia 發現尾側面板在平放和半折兩種姿態下都沒有用上那個豎向工具列,關閉按鈕可能會落在狀態顯示區和鏡頭下面。在 Natalia 的測試中,一行程式碼就解決了:

.sheet(item: $selection) { item in
    DetailSheet(item: item)
        .presentationPlacement(.trailing)
        .presentationDetents([.large])
}

Natalia 明確表示,這個 detent 是針對這一測試版的臨時解決辦法,並不是位置設定的必要條件。在照抄之前,先在自己的建置版本里檢查尾側面板,SDK 更新後再檢查一次。檢查清單裡包含了這一項。

Rob Swift 遇到了面板的另一種問題:自定義的高度沒有落在安全區域內。如果你手動設定高度,上線之前在展開、合上和半折三種狀態下都看一看。

浮動按鈕由你來擺放

這是每個適配真實 App 的人都會碰上的缺口。Harshil 的展開佈局表現得像 iPad,合上時的佈局主要是處理新的水平內邊距。剩下的那個控制元件是一個浮動分享按鈕,懸而未決的問題是它需要多少內邊距才能和工具列對齊。

Rob Swift 的截圖展示了同一類 bug。兩個尾側欄項排得很奇怪。一個用自定義檢視畫的鈴鐺,把本該在頂部的按鈕擠到了它下面。一個浮動操作按鈕位置偏高,因為即使工具列已經離開了底邊,底部安全區域依然很高。Matthaus Woolard 回覆 Harshil 時建議,把那個分享按鈕放進 safeAreaBar(edge: .bottom),這樣滾動檢視會自動加上內邊距,欄會在區域性模糊,內邊距也由系統提供。Matthaus 還認為,在有空間時,Apple 更希望你把“分享”移進側邊欄。

Rob Swift@RobSwish

I use size classes and I'm still finding the Duo to be completely different animal than iPhone or iPad. Here you see 2 right bar button items laying out very oddly on the Duo.

Image from Rob Swift's post

如果底部內邊距仍在為一個已經不在底部的工具列預留空間,就把它當作 bug 提交回饋,Matthaus 在那條貼文裡也是這樣建議的。然後再想想這個按鈕是否應該做成工具列項。一個往列表裡新增內容的加號按鈕,更多時候應該作為工具列操作跟著那個列表,而不是一個在 ZStack 裡定位的圓形按鈕。

Sahil AK 對 TimeWave 的適配是一個簡短的例子:原生工具列 API 完成了工作,剩下的是那個浮動加號。貼文見設計指南。

速查表

你想要 SwiftUI UIKit
讓自定義項加入側邊欄 在項上使用 .axisBehavior(.verticalPreferred)
即使有圖示也讓項保持橫向 .axisBehavior(.horizontalOnly)
為側邊欄繪製自定義檢視 讀取 @Environment(\.toolbarVerticalEdge) traitCollection.verticalBarEdge
把關閉按鈕放在側邊欄頂部 .cancellationAction 位置 前側項組,leftItemsSupplementBackButton = false
不讓主要操作進入溢位選單 .topBarPinnedTrailing navigationItem.pinnedTrailingGroup
把“圖示加數字”的檢視變成圖示和標記 .badge() UIBarButtonItem.badge
選擇先收起標籤還是操作 .toolbarVerticalCompressionBehavior(.prefersTabBar) 或 .prefersToolbarItems
選擇哪些項能在擠壓中留下 .visibilityPriority()
把自己的操作併入系統溢位選單 ToolbarOverflowMenu additionalOverflowItems
把各種欄送回頂部和底部 .toolbarVerticalBehavior(.disabled) preferredVerticalBarBehavior 返回 .disabled
設定面板位置 .presentationPlacement(.leading / .center / .trailing)

UIKit 列留空的,是上面這些資料只在 SwiftUI 中介紹過的功能。在認定沒有對應功能之前,先查一下 Apple 的 UIKit 文件。

繼續閱讀

App · 規格閱讀約 4 分鐘

iPhone Duo 能執行 iPad App 嗎?iPhone Duo 與 iPad mini

iPhone Duo 的內螢幕幾乎有 iPad mini 那麼大,但它執行的是 iOS,不是 iPadOS。這對僅限 iPad 的 App 意味著什麼、為什麼很多 Duo App 看起來會像它們的 iPad 版,以及 Duo 從 iPad 借來了什麼。

10月6日閱讀指南