iPhone Duo 的竖向栏
iPhone Duo 如何把导航栏、工具栏和标签栏移到侧边,哪些容器符合条件,以及如何摆放面板和浮动按钮。
在 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。

Sarun W. 根据 Raise the Bar 讲座补充了一个在多栏分栏视图(NavigationSplitView 这类布局,不是多任务的分屏浏览)中很容易忽略的限制:只有当栏所在的容器贴着屏幕边缘时,栏才会移动。详情栏会拿到竖向栏,其他栏仍然是横向的,展开的检查器也是如此,以免与详情栏争抢。如果某个按钮“不肯”移动,在动用关闭选项之前,先看看它在哪一栏里。

这一列属于硬件,而不属于阅读方向。在从右到左书写的语言中,它仍然在设备的同一侧,由内容围绕它来适配。在列内,从上到下保持 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,合上时的布局主要是处理新的水平内边距。剩下的那个控件是一个浮动分享按钮,悬而未决的问题是它需要多少内边距才能和工具栏对齐。
The only tricky thing left is how to handle floating UI like this share button It's tricky to figure out some heuristics for how much padding those need to have to align with toolbars
Rob Swift 的截图展示了同一类 bug。两个尾侧栏项排得很奇怪。一个用自定义视图画的铃铛,把本该在顶部的按钮挤到了它下面。一个浮动操作按钮位置偏高,因为即使工具栏已经离开了底边,底部安全区域依然很高。Matthaus Woolard 回复 Harshil 时建议,把那个分享按钮放进 safeAreaBar(edge: .bottom),这样滚动视图会自动加上内边距,栏会在局部模糊,内边距也由系统提供。Matthaus 还认为,在有空间时,Apple 更希望你把“分享”移进侧边栏。
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.
如果底部内边距仍在为一个已经不在底部的工具栏预留空间,就把它当作 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 文档。
继续阅读

iPhone Duo App 创意:开发者正在用折叠屏做的 6 件事
300 多个 iPhone Duo 演示、概念和原型,归纳为反复出现的六种模式,从半折的笔记本电脑形态到把铰链当控制器。如果你正在考虑做点什么,可以从这里开始。

iPhone Duo 能运行 iPad App 吗?iPhone Duo 与 iPad mini
iPhone Duo 的内屏几乎有 iPad mini 那么大,但它运行的是 iOS,不是 iPadOS。这对仅限 iPad 的 App 意味着什么、为什么很多 Duo App 看起来会像它们的 iPad 版,以及 Duo 从 iPad 借来了什么。

iPhone Duo 游戏:即将到来的作品,以及折叠屏给游戏带来了什么
哪些游戏已在 iPhone Duo 上亮相、未更新的游戏在展开的屏幕上是什么样子,以及开发者正在用铰链、半折和两块屏幕做出哪些新玩法。