WWDC26:把 SwiftUI Toolbar 真正握在手心里

引子

你有没有过这种时刻:同一套 .toolbar { },在 Mac 上干干净净排成一排,一到 iPhone 上就「失踪」了半截------有的按钮还在,有的钻进了 菜单,你却说不清系统到底按什么规则做的选择。

Toolbar 大概是 SwiftUI 里我写得最多、吐槽得也最多的 API 之一。声明式、自适应,听起来很香;真要做跨平台产品时,你才会发现:系统替你做的「聪明决定」,有时刚好不是你想要的。

尤其现在这套设计语言把界面拆成内容层和控制层,工具栏几乎成了控制层的门面------它往哪摆、谁先露脸、滚动时收不收,都会直接影响手感。

这周我们聊的,就是 WWDC 之后那批新 API:让你在「交给系统」和「我说了算」之间,终于能拨得更准一点。

到底 SwiftUI 新升级的 Toolbar 有什么玄机?让我们马上来先睹为快吧?;-)


先从一个朴素例子说起。

Toolbar API 很声明式,也很会自适应;可一旦按钮一多,你就会想要更多控制权。比如这样一个塞满动作的工具栏------在 macOS、iOS、iPadOS 上,观感可能完全不是一回事:

swift 复制代码
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Hello")
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        Button("Action 1") {

                        }
                    }

                    ToolbarItemGroup {
                        Button("Action 2") {

                        }

                        Button("Action 3") {

                        }

                        Button("Action 4") {

                        }

                        Button("Action 5") {

                        }
                    }
                }
        }
    }
}

同一段代码,平台会给出不同反应。在 macOS 上,这些动作往往都挤在右上角;

在 iOS 上,则可能一部分留在 trailing,另一部分被收进 overflow 菜单。

你写的是「一堆按钮」,系统交付的却是「它认为合适的布局」。


所以 SwiftUI 加了 visibilityPriority,可以挂在 ToolbarContent 上。可选值包括 automaticlowhigh 这类优先级。空间紧张时,系统会参考优先级,优先折叠那些不那么重要的项:

swift 复制代码
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Hello")
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        Button("Action 1") {

                        }
                    }
                    .visibilityPriority(.high)

                    ToolbarItemGroup {
                        Button("Action 2") {

                        }

                        Button("Action 3") {

                        }

                        Button("Action 4") {

                        }

                        Button("Action 5") {

                        }
                    }
                }
        }
    }
}

换句话说:你不必再靠碰运气猜谁会留下------可以明确告诉系统,「Action 1 比旁边那几个更重要」。


再看一个更绕一点的例子。把第二组放进 .secondaryAction 之后,平台差异会更扎眼:macOS 上,Action 1 通常在右上,其余一组可能居中出现在导航区域;

iOS 上,Action 1 会钉在 trailing,其它项则更容易被折叠起来。

swift 复制代码
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Hello")
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        Button("Action 1") {

                        }
                    }

                    ToolbarItemGroup(placement: .secondaryAction) {
                        Button("Action 2") {

                        }

                        Button("Action 3") {

                        }

                        Button("Action 4") {

                        }

                        Button("Action 5") {

                        }
                    }
                }
        }
    }
}

自适应仍然在,但语义更清楚了:主操作归主操作,次要操作归次要操作。

跨平台时,这种「角色分工」往往比硬编码坐标更经得起折腾。


有时候你根本不想让系统猜。

某些动作本来就该住在 overflow 里------不常用,但要找得到。这时可以用专门的 ToolbarOverflowMenu

swift 复制代码
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Hello")
                .toolbar {
                    ToolbarItemGroup(placement: .primaryAction) {
                        Button("Action 1") {

                        }
                    }

                    ToolbarOverflowMenu {
                        Button("Action 2") {

                        }

                        Button("Action 3") {

                        }

                        Button("Action 4") {

                        }

                        Button("Action 5") {

                        }
                    }
                }
        }
    }
}

它表达的意图很干脆:这组内容默认折叠。你不用再盯着预览猜「这次会不会被收进去」------你已经主动把它们放进菜单了。

(补充一句:按当前 SDK,ToolbarOverflowMenu 主要面向 iOS / visionOS;文章里说「全平台默认折叠」,更接近设计意图,落地时仍要以平台可用性为准。)


还有些按钮几乎不能丢,比如分享。

SwiftUI 为此提供了 .topBarPinnedTrailing:把项钉在顶部栏 trailing。通常只有在搜索展开、空间实在不够时,它才可能被挤掉:

swift 复制代码
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Hello")
                .toolbar {
                    ToolbarItemGroup(placement: .topBarPinnedTrailing) {
                        Button("Action 1") {

                        }
                    }

                    ToolbarItemGroup(placement: .secondaryAction) {
                        Button("Action 2") {

                        }

                        Button("Action 3") {

                        }

                        Button("Action 4") {

                        }

                        Button("Action 5") {

                        }
                    }
                }
        }
    }
}

优先级解决「谁更重要」,pin 解决「谁必须在场」。

一个管排序,一个管锚定,搭配起来很好用。


最后一块拼图,是滚动时的收纳。列表一长,你往往希望导航栏别一直霸占屏幕。可以这样写:

swift 复制代码
struct ContentView: View {
    var body: some View {
        NavigationStack {
            Text("Hello, World!")
                .navigationTitle("Hello")
                .toolbar {
                    ToolbarItemGroup(placement: .topBarPinnedTrailing) {
                        Button("Action 1") {

                        }
                    }

                    ToolbarItemGroup(placement: .secondaryAction) {
                        Button("Action 2") {

                        }

                        Button("Action 3") {

                        }

                        Button("Action 4") {

                        }

                        Button("Action 5") {

                        }
                    }
                }
                // 原文写作 toolbarMinimizeBehavior;
                // 当前 SDK 符号为 toolbarMinimizationBehavior,且应加在栈内滚动内容上。
                .toolbarMinimizationBehavior(.onScrollDown, for: .navigationBar)
        }
    }
}

向下滚时收起导航栏,内容区立刻更敞亮。

同类能力也可以作用在 tab、bottom、window 等工具栏上------滚动与 chrome 终于能更明确地「配合演出」。


回到开头那个烦恼:按钮忽然进了 ,你却插不上话。现在这套新修饰符,把系统自适应和显式控制重新校准了一遍。你可以指定谁更重要、谁尽量一直可见、谁就该住在 overflow,以及滚动时工具栏怎么让路。

Toolbar 还是那个 Toolbar,但它不再只是「写完等系统发挥」的黑盒。把关键旋钮拧到自己顺手的位置,界面才会既像系统原生,又像你的产品。

希望这篇文章对大家有用。有问题欢迎给我留言。

感谢宝子们的观赏,下回再见!8-)

相关推荐
大熊猫侯佩6 小时前
WWDC 26 全新 ResultsObserver:终于能在 View 外面盯着 SwiftData 了
swiftui·swift·apple
大熊猫侯佩6 小时前
别再把大模型供在云端了,WWDC26 CoreAI 大模型下凡实战
ai编程·swift·wwdc
00后程序员张7 小时前
有没有更轻量的 iOS 开发环境?快蝎kxapp轻量化路径与构成
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
sakiko_1 天前
Swift学习笔记39-实战注意事项
前端·笔记·学习·swift
东坡肘子1 天前
Apple Intelligence 已通过审核,即将在中国提供服务 -- 肘子的 Swift 周报 #148
人工智能·swiftui·swift
初级代码游戏3 天前
iOS开发 Swift 速记7:结构体和类
开发语言·ios·swift
初级代码游戏4 天前
iOS开发 Swift 速记5:高级运算符
ios·移动开发·swift
初级代码游戏4 天前
iOS开发 Swift 速记4:函数与闭包
开发语言·ios·swift
ZacJi5 天前
当苹果将系统 API 划归 @MainActor:一次跨 SDK 版本的 Swift 并发踩坑与破局实战
ios·swift