概述
在 SwiftUI 的界面布局中,列表(List)和 Form 是我们秃头码农们司空见惯的选择。不过大家是否知道:如果将 Picker 之类的视图嵌入到列表或 Form 的子项中会导致导航操作无法被触发。
从上图可以看到:当在 List 的子项中嵌入 Picker 时,所有互动操作都会聚焦在 Picker 上面,从而使得导航根本无法触发。
这种现象在 SwiftUI 6.0(iOS 18.1)中依然存在。
在本篇博文中,您将学到如下内容:
想知道如何解决吗?超乎寻常的简单!
那还等什么呢?Let's go!!!😉
1. 一个简单的问题
首先是一段平淡无奇的的源代码:
swift
enum JailConfig: Int, Identifiable, CaseIterable {
case veryEasy = 1
case easy
case normal
case hard
case veryHard
var id: Int {
rawValue
}
var desc: String {
switch self {
case .veryEasy:
"非常容易"
case .easy:
"容易"
case .normal:
"一般"
case .hard:
"有点难"
case .veryHard:
"很难"
}
}
}
@available(iOS 17, *)
struct V3_GameView: View {
@State var jailConfig = JailConfig.normal
var body: some View {
NavigationStack {
List {
NavigationLink("每日打卡") {
ClockInView()
}
NavigationLink("抓住机会") {
PressBlessingView(config: .normal)
}
NavigationLink {
FingersJail(config: jailConfig)
} label: {
VStack(alignment: .leading) {
Text("指尖监狱")
LabeledContent {
Picker("", selection: $jailConfig) {
ForEach(JailConfig.allCases) { config in
Text(config.desc).tag(config)
}
}
} label: {
Text("难度")
}
}
}
}
.listStyle(.plain)
.navigationTitle("游戏")
.toolbar {
Text("大熊猫侯佩 @ \(Text("CSDN").foregroundStyle(.red))")
.foregroundStyle(.gray)
.font(.headline.bold())
}
}
}
}
从代码中可以看到:我们在第 3 个列表项中嵌入了一个 Picker 视图,并将其包裹在 NavigationLink 的 label 中。这样做的意图是让用户在导航至子视图之前可以先选择一些配置信息(比如游戏难度)。
但这样简单的组合却带来了意想不到的"不良"结果:我们现在只能选择 Picker 的内容而无法进行导航了。
2. "肿么"会这样?
为了理解为何会如此,让我们先将布局稍微做一下调整。这次我们将 Picker 放在 NavigationLink 的外部:
swift
VStack {
NavigationLink {
FingersJail(config: jailConfig)
} label: {
Text("指尖监狱")
}
LabeledContent {
Picker("", selection: $jailConfig) {
ForEach(JailConfig.allCases) { config in
Text(config.desc).tag(config)
}
}
} label: {
Text("难度")
}
}
再次运行代码可以看到:结果和之前一毛一样。为了确保这不是 Xcode 预览中的一个 Bug,我特地在模拟器中也运行了一下,毫无二致。
诸多关于 Xcode "蛋疼" 预览(Preview)机制的进一步介绍,请小伙伴们移步如下链接观赏精彩的内容:
- SwiftUI 界面动画调试一例:做码农最重要的是什么?相信自己!
- Xcode13模拟器和预览(Preview)导致Mac处理器占用率急剧飙升的解决方法
- SwiftUI Xcode项目新增单元测试(Unit Test)后预览(Preview)崩溃的解决
- Xcode预览(Preview)显示List视图内容的一个Bug及解决
- Xcode如何在预览(Preview)调试中避免与SwiftUI正常运行时环境不一致导致的崩溃
- Xcode编写SwiftUI代码时一个编译通过但导致预览(Preview)崩溃的小陷阱
- Xcode 15 预览 SwiftUI 视图中 @FetchRequest 查询结果不能正确刷新的解决
看来目前 SwiftUI 布局中,在列表(或 Form)子项里 VStack(或其它容器)内部如果有类似 Picker 之类的可交互视图,其它视图的交互性将会受到抑制(除非其它视图是 borderless 样式的按钮)。
知道了原因解决起来就十分简单了:只需把它们分开就行啦!
解决之道
如下代码所示,我们可以将 Picker 和原先列表子项中显示的内容完全"分离":
swift
NavigationLink {
FingersJail(config: jailConfig)
} label: {
Text("指尖监狱")
}
LabeledContent {
Picker("", selection: $jailConfig) {
ForEach(JailConfig.allCases) { config in
Text(config.desc).tag(config)
}
}
} label: {
Text("难度")
}
.padding(.top, -10)
不过,这样从界面上看会略显"割裂感":
于是乎,我们可以用视图的 listRowSeparator 修改器隐藏中间的分隔线:
swift
NavigationLink {
FingersJail(config: jailConfig)
} label: {
Text("指尖监狱")
}
.listRowSeparator(.hidden)
现在效果好极了:
或者我们可以将 Picker 和 NavigationLink 的内容统统放到一个 Section 中去,这样代码组织性会更好一些:
swift
Section {
NavigationLink {
V3_FingersJail(config: jailConfig)
} label: {
Text("指尖监狱")
}
.listRowSeparator(.hidden)
LabeledContent {
Picker("", selection: $jailConfig) {
ForEach(V3_JailConfig.allCases) { config in
Text(config.desc).tag(config)
}
}
} label: {
Text("难度")
}
.padding(.top, -10)
}
至此,我们成功的解决了博文开头那个问题!希望可以一解小伙伴们的燃眉之急,棒棒哒!💯
想要系统学习 Swift 的小伙伴们,欢迎来我的《Swift 语言开发精讲》专栏逛一逛哦:
总结
在本篇博文中,我们讨论了 SwiftUI 列表(或 Form)子项中的 Picker 导致无法导航跳转的原因,并随后给出完美的解决方案。
感谢观赏,再会吧!😎