SwiftUI 列表(或 Form)子项中的 Picker 引起导航无法跳转的原因及解决

概述

在 SwiftUI 的界面布局中,列表(List)和 Form 是我们秃头码农们司空见惯的选择。不过大家是否知道:如果将 Picker 之类的视图嵌入到列表或 Form 的子项中会导致导航操作无法被触发。

从上图可以看到:当在 List 的子项中嵌入 Picker 时,所有互动操作都会聚焦在 Picker 上面,从而使得导航根本无法触发。

这种现象在 SwiftUI 6.0(iOS 18.1)中依然存在。

在本篇博文中,您将学到如下内容:

  • 概述
  • [1. 一个简单的问题](#1. 一个简单的问题)
  • [2. "肿么"会这样?](#2. “肿么”会这样?)
  • 解决之道
  • 总结

想知道如何解决吗?超乎寻常的简单!

那还等什么呢?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 布局中,在列表(或 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 导致无法导航跳转的原因,并随后给出完美的解决方案。

感谢观赏,再会吧!😎

相关推荐
轩情吖1 天前
Python基础知识点完整总结
python·字符串·编程语言·函数·列表·元组·字典
小刘在重生~1 天前
Java 集合|Collection、List、ArrayList、LinkedList、泛型、Collections 工具类
java·数据结构·list
2601_962299881 天前
排序,然后再使用
python·排序·列表·关键函数·装饰-排序-去装饰
东坡肘子1 天前
当 Mac mini 的价格不再 mini -- 肘子的 Swift 周报 #152
人工智能·swiftui·swift
暖焰核心3 天前
迭代器与模板的结合——stl的list
c++·windows·list
.YM.Z5 天前
C++ STL 容器深度剖析:vector 与 list 的模拟实现及对比
开发语言·c++·vector·list
重生之小比特5 天前
【初阶C++】list
开发语言·c++·list
2301_800074216 天前
map,list简单方法
windows·python·list
xxwxx__6 天前
C++ list 深度解析:从使用原理到模拟实现
开发语言·c++·list
鹿角片ljp6 天前
LeetCode 56:合并区间复盘|从排序思维到 List<int[]> 的简洁写法
算法·leetcode·list