SwiftUI Settings Scene 避坑指南:告别繁琐,打造丝滑体验
说起macOS的App开发,谁还没有被它极其复杂的窗口管理和菜单栏机制搞的头大,不同于用户群体庞大的iOS、iPadOS,macOS的市场占有率还是太少了,导致与其相关的开发资料并不多,macOS的很多特性并没有得到充分的理解和使用,比如我最近在开发时使用的、官方默认推出的、规范化的设置窗口Settings Scene,在使用过程中踩了不少坑,今天就来跟大家聊聊我与 SwiftUI Settings Scene 的爱恨情仇,希望能帮到大家。
Settings Scene 是什么?为什么选择它?
Settings Scene其实不是什么新人,macOS 11.0+就已经支持,是SwiftUI的一种Scene,仅macOS可用,它的主要作用就是用于App的设置,几乎每个App都会有设置界面,这就是一个值得标准化的界面。很可惜,除了Apple自家的App外,它的使用率并不高,究其原因,如下:
- Settings Scene是可替代的,很多在macOS 11之前开发的App可能还在使用appkit的方法实现。swiftUI中,Window和WindowGroup也都可以替代;
- Settings Scene作为标准化Scene,更多考虑与系统集成度,这就导致它有很多限制,自由度低,在很多产品设计阶段就不会被采用;
- 败也萧何。由于macOS复杂的窗口管理机制,很多产品在开发时,只采用一个主窗口,避开多窗口的场景,Settings Scene是一个独立,但不完整的窗口,自然不会被采用;
反过来,成也萧何。正因为Settings Scene的标准化和系统的高度集成,这就缩短了开发时间,省时省力,且行为表现可预期,用户体验也很好。在最近几年新开发的App中看到它的身影,比如大火的浏览器Arc。即使macOS 26 Tahoe采用新的设计语言Liquid Glass,采用Settings Scene的App,可能几乎不用调整适配。
Settings Scene 的典型特征:只亮红灯的红绿灯
macOS的窗口左上角的控制按钮,因颜色与交通信号灯的颜色类似,被戏称为“红绿灯按钮”。不过,Settings Scene的红绿灯按钮出于标准化考虑,与Window、WindowGroup的红绿灯按钮有以下一些不同:
- 红绿灯只亮起了红灯:关闭按钮,并不具备最小化、最大化的功能,更不会有窗口的填充与排列功能;
- 更像是标准窗口的附属窗口。Settings Scene在被激活,处于最前端时,依然可以操作主窗口的红绿灯按钮。最小化主窗口后,在点击恢复窗口时,恢复的是Settings Scene,需要主动点击主窗口或关闭Setting Scene后,再点击可打开主窗口,合理但很奇怪的窗口管理机制。
只有一个settings窗口的便捷高效
使用Settings Scene的最大好处是,整个App全局只会存在一个窗口,无论你从哪里发起打开,只会进入这个唯一的窗口,这也就不需要考虑数据的不同步、不一致问题。
在使用Settings Scene后,系统会自动在菜单栏 App名称那栏增加设置按钮,以及设置窗口的标准快捷键「Command + , 」,并且,Settings Scene不会进入菜单栏的Window菜单栏,便捷高效。使用Window或WindowGroup实现设置窗口时,需要考虑专门处理以上情况。
Settings Scene 的窗口尺寸设置:可调,但不可拖动
Settings Scene作为一个附属窗口,正如红绿灯按钮没有最大化,最小化按钮一样,它无法随意拖动调整大小,也就是说,它的窗口尺寸是固定的,但是是可设置的,建议给它一个Default Size
。
当然不是说,给了一个Default Size
,窗口尺寸就固定了。据我在配合使用Tabview时观察到的情况来看,Settings Scene是一个.windowResizability(.contentSize)
的窗口。如以下代码举例:
//Settings设置defaultSize(width: 800, height: 600)
Settings {
SettingsView()
}
.defaultSize(width: 800, height: 600)
//SettingsView里使用含有2个Tab的Tabview
struct SettingsView: View {
var body: some View {
TabView {
AView()
.tabItem {
Label("A", systemImage: "doc.text")
}
BView()
.tabItem {
Label("A", systemImage: "cpu")
}
}
}
}
// BView
struct BView: View {
var body: some View {
VStack {
Text("B View - Coming Soon")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
//不设置.frame最大化会造成窗口挤压在一起,无法被拖拽回去
}
}
SettingsView设置.defaultSize(width: 800, height: 600)
的情况下,Tabview中包含2个Tab,A和B,在B视图中只有Text("B View - Coming Soon")
这段代码,显然内容无法撑起defaultSize,这时候,如果不设置.frame(maxWidth: .infinity, maxHeight: .infinity)
,从A Tab切换到B Tab时,会造成窗口挤压到Text("B View - Coming Soon")
这段文字的大小,再切换回A窗口时,由于窗口尺寸无法调整,窗口尺寸无法恢复defaultSize的大小。
因此在使用Settings Scene时,尽量通过.frame(maxWidth: .infinity, maxHeight: .infinity)
让content撑满这个窗口,避免窗口挤压。
注意
Spacer()
无法让content撑满窗口,它的主要作用是用来推动窗口内元素的位置的,无法影响窗口本身- Settings Scene + Tabview是官方文档里推荐的做法,这种组合下,窗口挤压问题更突出,因此要给每个Tabview里的Tab设置
.frame(maxWidth: .infinity, maxHeight: .infinity)
官方推荐:Settings Scene + Tabview
Settings Scene中要对不同设置项分类时,官方推荐的是使用Tabview,Tabview在不同系统下的表现各不相同,这是Apple生态的系统定死的,这就麻烦了。
Settings Scene + Tabview的方法下,会有以下特性:
- 每个Tab需要一个icon。纯使用Text时,会出现很奇怪现象:文字tab的上方会诡异地刚好留有一个icon尺寸的空档;
- Settings Scene的窗口标题是由Tabview的Tab名称决定的,除非你给每个Tab单独设置title
- Tabview样式几乎不能修改,比如无法直接设置背景色,除非你自定义tabview。tab被选中的颜色倒是可以通过
.accent( )
修改。
总结Settings Scene
总的来说,SwiftUI Settings Scene 对于设置不是重点的Mac App 来说,是一个非常方便的选择,尤其是需要快速验证功能可行性的App。它能帮你快速搭建一个美观、易用的设置界面,省时省力。但是,前文提到的一些限制,是在使用它时,不得不注意的。
希望这篇文章能帮大家更好地理解和使用 SwiftUI Settings Scene! 选择最适合自己 App 的方案才是最重要的。祝大家开发顺利!