无法在 List ForEach 中以编程方式激活 NavigationLink
Posted
技术标签:
【中文标题】无法在 List ForEach 中以编程方式激活 NavigationLink【英文标题】:Unable to activate NavigationLink programmatically, in a List ForEach 【发布时间】:2021-12-15 18:57:27 【问题描述】:在simple test project at Github 中,我显示一个 NavigationLink 项目列表:
GamesViewModel.swift 文件模拟了一个数字游戏 id 列表,通过我的真实应用程序中的 Websockets 获得:
class GamesViewModel: ObservableObject /*, WebSocketDelegate */
@Published var currentGames: [Int] = [2, 3]
@Published var displayedGame: Int = 0
func updateCurrentGames()
currentGames = currentGames.count == 3 ?
[1, 2, 3, 4] : [2, 5, 7]
func updateDisplayedGame()
displayedGame = currentGames.randomElement() ?? 0
我的问题是,当单击“加入随机游戏”按钮时,我试图以编程方式激活 ContentView.swift 中的随机 NavigationLink:
struct ContentView: View
@StateObject private var vm:GamesViewModel = GamesViewModel()
var body: some View
NavigationView
VStack
List
ForEach(vm.currentGames, id: \.self) gameNumber in
NavigationLink(destination: GameView(gameNumber: gameNumber)
/* , isActive: $vm.displayedGame == gameNumber */ )
Text("Game #\(gameNumber)")
Button(
action: vm.updateCurrentGames() ,
label: Text("Update games")
)
Button(
action: vm.updateDisplayedGame() ,
label: Text("Join a random game")
)
但是代码
ForEach(vm.currentGames, id: \.self) gameNumber in
NavigationLink(destination: GameView(gameNumber: gameNumber),
isActive: $vm.displayedGame == gameNumber )
Text("Game #\(gameNumber)")
无法编译:
无法将类型“Bool”的值转换为预期的参数类型“Binding”
甚至可以在ForEach
中使用$
吗?
我的上下文是,在我的真实应用程序中,Jetty 后端在 PostgreSQL 数据库中创建一个新游戏,然后将该新游戏的数字 id 发送到应用程序。并且应用程序应该显示该游戏,即以编程方式导航到 GameView.swift。
更新:
用户 jnpdx 和 Dhaval 都提出了有趣的解决方案(谢谢!) - 但是当 NavigationLink 在屏幕上可见时,它们仅适用于短列表。
对于更长的列表,当应为滚动到屏幕外的游戏编号激活 NavigationLink 时 - 它们不起作用!
我已经尝试实现自己的解决方案,通过在屏幕顶部使用 NavigationLink/EmptyView,以确保它始终可见并且可以被触发以转换到 vm.displayedGame
号码。
但是我的代码不起作用,即只能工作一次(也许我需要在导航回主屏幕后以某种方式设置displayedGame = 0
?)-
这里ContentView.swift:
struct ContentView: View
@StateObject private var vm:GamesViewModel = GamesViewModel()
var body: some View
NavigationView
VStack
NavigationLink(
destination: GameView(gameNumber: vm.displayedGame),
isActive: Binding(get: vm.displayedGame > 0 , set: _,_ in )
)
EmptyView()
List
ForEach(vm.currentGames, id: \.self) gameNumber in
NavigationLink(
destination: GameView(gameNumber: gameNumber)
)
Text("Game #\(gameNumber)")
Button(
action: vm.updateCurrentGames() ,
label: Text("Update games")
)
Button(
action: vm.updateDisplayedGame() ,
label: Text("Join a random game")
)
【问题讨论】:
【参考方案1】:问题是您需要一个Binding<Bool>
——而不仅仅是一个简单的布尔条件。添加$
可以绑定到displayedGame
(即Binding<Int?>
),而不是Binding<Bool>
,这是NavigationLink
所期望的。
一种解决方案是为您正在寻找的条件创建一个自定义绑定:
class GamesViewModel: ObservableObject /*, WebSocketDelegate */
@Published var currentGames: [Int] = [2, 3]
@Published var displayedGame: Int?
func navigationBindingForGame(gameNumber: Int) -> Binding<Bool>
.init
self.displayedGame == gameNumber
set: newValue in
self.displayedGame = newValue ? gameNumber : nil
func updateCurrentGames()
currentGames = currentGames.count == 3 ? [1, 2, 3, 4] : [2, 5, 7]
func updateDisplayedGame()
displayedGame = currentGames.randomElement() ?? 0
struct ContentView: View
@StateObject private var vm:GamesViewModel = GamesViewModel()
var body: some View
NavigationView
VStack
NavigationLink(isActive: vm.navigationBindingForGame(gameNumber: vm.displayedGame ?? -1))
GameView(gameNumber: vm.displayedGame ?? 0)
label:
EmptyView()
List
ForEach(vm.currentGames, id: \.self) gameNumber in
NavigationLink(destination: GameView(gameNumber: gameNumber),
isActive: vm.navigationBindingForGame(gameNumber: gameNumber)
)
Text("Game #\(gameNumber)")
Button(
action: vm.updateCurrentGames() ,
label: Text("Update games")
).padding(4)
Button(
action: vm.updateDisplayedGame() ,
label: Text("Join a random game")
).padding(4)
.navigationBarTitle("Select a game")
我将displayedGame
更改为Int?
,因为我认为在语义上,如果没有显示的游戏,将其设置为 Optional 比设置为 0
更有意义,但如果需要,可以很容易地改回来.
【讨论】:
IIRC,这仅适用于屏幕上可见的 NavigationLinks @AlexanderFarber 我已经更新了我的答案——你在正确的轨道上添加了一个 NavigationLink 在顶部,但你还必须使用绑定的set
侧——否则它'只会工作一次。
也许……我必须做一些测试。在 iPad(或 Mac)上可能存在显示两列的情况,并且突出显示仍然正确是很重要的。
两列中的突出显示似乎并不能真正在两列导航中可靠地工作......也许还有另一个技巧
当用户从游戏页面返回时调用。【参考方案2】:
你也这样过
ForEach(vm.currentGames, id: \.self) gameNumber in
NavigationLink(destination: GameView(gameNumber: gameNumber),
isActive: Binding(get:
vm.displayedGame == gameNumber
, set: (v) in
vm.displayedGame = v ? gameNumber : nil
))
Text("Game #\(gameNumber)")
【讨论】:
真的有用吗?.constant
可能永远不会改变?我在my test project 中尝试过您的建议,单击“加入随机游戏”按钮几次后,它会产生不稳定的行为。
@AlexanderFarber 我检查我的代码并更新答案请检查
这也行得通! (遗憾的是,也仅适用于屏幕上显示的 NavigationLinks)。谢谢,点赞以上是关于无法在 List ForEach 中以编程方式激活 NavigationLink的主要内容,如果未能解决你的问题,请参考以下文章