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 · 规格阅读约 3 分钟

iPhone Duo 能运行 iPad App 吗?iPhone Duo 与 iPad mini

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

10月6日阅读指南