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 导致无法导航跳转的原因,并随后给出完美的解决方案。

感谢观赏,再会吧!😎

相关推荐
YQ_ZJH16 小时前
Java List列表创建方法大总结
java·开发语言·数据结构·算法·list
Vect__17 小时前
list 迭代器:C++ 容器封装的 “行为统一” 艺术
c++·list
大熊猫侯佩17 小时前
雪山飞狐之 Swift 6.2 并发秘典:@concurrent 的江湖往事
swiftui·swift·apple
逐云者1231 天前
低空经济:从政策热词到生活日常——中国低空经济全景解析与杭深模式对比
生活·无人机·导航·低空经济
---学无止境---1 天前
九、内核数据结构之list
linux·数据结构·list
用户092 天前
Swift Concurrency 中的 Threads 与 Tasks
ios·swiftui·swift
埃伊蟹黄面3 天前
STL 容器 --- list 类
开发语言·c++·list
Gerlat小智3 天前
【Python精讲 03】Python核心容器:一篇通关序列(List, Tuple)、映射(Dict)与集合(Set)
windows·python·list
hnjzsyjyj3 天前
AcWing 827:双链表 ← STL list
数据结构·链表·list·双链表
Onesoft%J1ao3 天前
C++ 1.STL-vector 2.STL-list 3.数组模拟单向链表 详解配例题 通俗易懂
c++·青少年编程·list