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

感谢观赏,再会吧!😎

相关推荐
秋已杰爱23 分钟前
list底层实现细节
list
被二进制支配的打工人17 小时前
【STL】list 双向循环链表的使用介绍
数据结构·c++·链表·stl·list
wangchen_01 天前
C++ List 容器:实现原理深度解析
数据结构·c++·链表·list
KpLn_HJL1 天前
leetcode - 82. Remove Duplicates from Sorted List II
算法·leetcode·list
阿华的代码王国2 天前
【算法】集合List和队列
java·算法·list·集合和队列
五味香2 天前
Java学习,List移动元素
android·java·开发语言·python·学习·kotlin·list
_extraordinary_4 天前
list的模拟实现详解
数据结构·windows·list
晓风残月( ̄ε(# ̄)~5 天前
Spring参数校验,数组入参校验 :List<E>
java·经验分享·spring boot·后端·spring·spring cloud·list
Yeats_Liao5 天前
Java List过滤 Stream API filter() 应用
java·开发语言·list
Future_yzx6 天前
1️⃣Java中的集合体系学习汇总(List/Map/Set 详解)
java·学习·list