如果未显示所有 NavigationLink,则 NavigationView 中的 SwiftUI 的 NavigationLink `tag` 和 `selection` 停止工作
SwiftUI's NavigationLink `tag` and `selection` in NavigationView stops working if not all NavigationLinks are displayed
我在 NavigationView
中有一个 Form
中的项目列表,每个项目都有一个可以通过 NavigationLink
访问的详细视图。
当我向列表中添加一个新元素时,我想显示它的详细视图。为此,我使用 NavigationLink
作为 selection
接收的 @State var currentSelection
,并且每个元素都具有作为 tag
:
的功能
NavigationLink(
destination: DetailView(entry: entry),
tag: entry,
selection: $currentSelection,
label: { Text("The number \(entry)") })
这行得通,并且遵循 Apple docs and the best practises。
令人惊讶的是,当列表中的元素多于屏幕上显示的元素(加上 ~2)时,它 停止 工作。问题:为什么?我该如何解决?
我做了一个最小的例子来复制这个行为:
import SwiftUI
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
var body: some View {
NavigationView {
Form {
ForEach(entries.sorted(), id: \.self) { entry in
NavigationLink(
destination: DetailView(entry: entry),
tag: entry,
selection: $currentSelection,
label: { Text("The number \(entry)") })
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) { Button("Add low") {
let newEntry = (entries.min() ?? 1) - 1
entries.insert(newEntry, at: 1)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) { Button("Add high") {
let newEntry = (entries.max() ?? 50) + 1
entries.append(newEntry)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Text("The current selection is \(String(describing: currentSelection))")
}
}
}
}
}
struct DetailView: View {
let entry: Int
var body: some View {
Text("It's a \(entry)!")
}
}
(我通过将列表减少到5项并在标签上设置padding来排除个元素是核心问题:label: { Text("The number \(entry).padding(30)") })
)
正如您在屏幕录像中看到的那样,在达到元素的临界数量后(通过添加到列表中或添加到列表中),底部的 sheet 仍然显示 currentSelection
是正在更新,但没有导航发生。
我使用了 iOS 14.7.1、Xcode 12.5.1 和 Swift 5.
问题出在编程导航(由工具栏中的按钮启动)
您的 NavigationLink 需要存在才能被触发。
但是如果使用 List
(或 Form,或 LazyVStack),则不应该“创建”不应该出现在屏幕上的子视图(此处:NavigationLink
s) ”。因此,当您尝试触发 NavigationLink(修改 currentSelection)时,此 NavigationLink 可能不存在
如果我们用 ScrollView 替换 List / Form,问题应该会消失。
但最好的方法当然是将两种导航方式分开:
1- 程序导航,由按钮触发。为此,我们将使用 NavigationLink(_:destination:isActive:)
初始化器。
2- 通过列表导航(可点击的单元格,又名 NavigationLink(destination:label:
)
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
@State var linkIsActive = false
var body: some View {
NavigationView {
Form {
ForEach(entries.sorted(), id: \.self) { entry in
// 2- Clickable label
NavigationLink(
destination: DetailView(entry: entry),
label: { Text("The number \(entry)") })
}
}
.background(
// 1- Programmatic navigation
NavigationLink("", destination: DetailView(entry: currentSelection ?? -99), isActive: $linkIsActive)
)
.onChange(of: currentSelection, perform: { value in
if value != nil {
linkIsActive = true
}
})
.onChange(of: linkIsActive, perform: { value in
if !value {
currentSelection = nil
}
})
//...
}
发生这种情况是因为未呈现较低的项目,因此在层次结构中没有 NavigationLink
带有此类标签
我建议你使用 ZStack
+ EmptyView
NavigationLink
“hack”。
此外,我在这里使用 LazyView,感谢 @autoclosure
,它让我可以通过向上包装 currentSelection
:这只会在 NavigationLink
处于活动状态时调用,并且发生在currentSelection != nil
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
var body: some View {
NavigationView {
ZStack {
EmptyNavigationLink(
destination: { DetailView(entry: [=10=]) },
selection: $currentSelection
)
Form {
ForEach(entries.sorted(), id: \.self) { entry in
NavigationLink(
destination: DetailView(entry: entry),
label: { Text("The number \(entry)") })
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) { Button("Add low") {
let newEntry = (entries.min() ?? 1) - 1
entries.insert(newEntry, at: 1)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) { Button("Add high") {
let newEntry = (entries.max() ?? 50) + 1
entries.append(newEntry)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Text("The current selection is \(String(describing: currentSelection))")
}
}
}
}
}
}
struct DetailView: View {
let entry: Int
var body: some View {
Text("It's a \(entry)!")
}
}
public struct LazyView<Content: View>: View {
private let build: () -> Content
public init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
public var body: Content {
build()
}
}
struct EmptyNavigationLink<Destination: View>: View {
let lazyDestination: LazyView<Destination>
let isActive: Binding<Bool>
init<T>(
@ViewBuilder destination: @escaping (T) -> Destination,
selection: Binding<T?>
) {
lazyDestination = LazyView(destination(selection.wrappedValue!))
isActive = .init(
get: { selection.wrappedValue != nil },
set: { isActive in
if !isActive {
selection.wrappedValue = nil
}
}
)
}
var body: some View {
NavigationLink(
destination: lazyDestination,
isActive: isActive,
label: { EmptyView() }
)
}
}
查看有关 LazyView 的更多信息,它通常对 NavigationLink
有帮助:在实际应用程序中,目标可能是一个巨大的屏幕,当每个单元格中都有一个 NavigationLink
时,SwiftUI 将处理所有可能导致滞后的问题
因为可能需要查看表单中添加了哪一行。我想展示一个解决方案,首先滚动到新插入的项目,然后显示详细视图。
虽然这不是我最喜欢的动画或流程。
该解决方案添加了一个额外的 ScrollViewReader,它似乎也可以完美地与 Forms 一起使用。我们要滚动到的视图是 NavigationLink(Label 不起作用,因为它在未显示时未实例化)。
注意
我偶尔会遇到一些故障,可能是Playgrounds。此外,在 Xcode 13 beta 4 上,控制台中发出警告,表明 NavigationLink 存在问题,我们应该提交错误报告。嗯...
我怀疑在执行滚动动画和随后的推送导航时可能存在问题。需要更多调查。
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
var body: some View {
NavigationView {
ScrollViewReader { proxy in
Form {
ForEach(entries.sorted(), id: \.self) { entry in
NavigationLink(
destination: DetailView(entry: entry),
tag: entry,
selection: $currentSelection,
label: { Text("The number \(entry)") }
)
.id("-\(entry)-")
}
}
.onChange(of: currentSelection) { newValue in
withAnimation {
if let currentSelection = self.currentSelection {
proxy.scrollTo("-\(currentSelection)-")
}
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) { Button("Add low") {
let newEntry = (entries.min() ?? 1) - 1
entries.insert(newEntry, at: 1)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) { Button("Add high") {
let newEntry = (entries.max() ?? 50) + 1
entries.append(newEntry)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Text("The current selection is \(String(describing: currentSelection))")
}
}
}
}
}
}
struct DetailView: View {
let entry: Int
var body: some View {
Text("It's a \(entry)!")
}
}
我在 NavigationView
中有一个 Form
中的项目列表,每个项目都有一个可以通过 NavigationLink
访问的详细视图。
当我向列表中添加一个新元素时,我想显示它的详细视图。为此,我使用 NavigationLink
作为 selection
接收的 @State var currentSelection
,并且每个元素都具有作为 tag
:
NavigationLink(
destination: DetailView(entry: entry),
tag: entry,
selection: $currentSelection,
label: { Text("The number \(entry)") })
这行得通,并且遵循 Apple docs and the best practises。
令人惊讶的是,当列表中的元素多于屏幕上显示的元素(加上 ~2)时,它 停止 工作。问题:为什么?我该如何解决?
我做了一个最小的例子来复制这个行为:
import SwiftUI
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
var body: some View {
NavigationView {
Form {
ForEach(entries.sorted(), id: \.self) { entry in
NavigationLink(
destination: DetailView(entry: entry),
tag: entry,
selection: $currentSelection,
label: { Text("The number \(entry)") })
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) { Button("Add low") {
let newEntry = (entries.min() ?? 1) - 1
entries.insert(newEntry, at: 1)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) { Button("Add high") {
let newEntry = (entries.max() ?? 50) + 1
entries.append(newEntry)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Text("The current selection is \(String(describing: currentSelection))")
}
}
}
}
}
struct DetailView: View {
let entry: Int
var body: some View {
Text("It's a \(entry)!")
}
}
(我通过将列表减少到5项并在标签上设置padding来排除个元素是核心问题:label: { Text("The number \(entry).padding(30)") })
)
正如您在屏幕录像中看到的那样,在达到元素的临界数量后(通过添加到列表中或添加到列表中),底部的 sheet 仍然显示 currentSelection
是正在更新,但没有导航发生。
我使用了 iOS 14.7.1、Xcode 12.5.1 和 Swift 5.
问题出在编程导航(由工具栏中的按钮启动) 您的 NavigationLink 需要存在才能被触发。
但是如果使用 List
(或 Form,或 LazyVStack),则不应该“创建”不应该出现在屏幕上的子视图(此处:NavigationLink
s) ”。因此,当您尝试触发 NavigationLink(修改 currentSelection)时,此 NavigationLink 可能不存在
如果我们用 ScrollView 替换 List / Form,问题应该会消失。
但最好的方法当然是将两种导航方式分开:
1- 程序导航,由按钮触发。为此,我们将使用 NavigationLink(_:destination:isActive:)
初始化器。
2- 通过列表导航(可点击的单元格,又名 NavigationLink(destination:label:
)
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
@State var linkIsActive = false
var body: some View {
NavigationView {
Form {
ForEach(entries.sorted(), id: \.self) { entry in
// 2- Clickable label
NavigationLink(
destination: DetailView(entry: entry),
label: { Text("The number \(entry)") })
}
}
.background(
// 1- Programmatic navigation
NavigationLink("", destination: DetailView(entry: currentSelection ?? -99), isActive: $linkIsActive)
)
.onChange(of: currentSelection, perform: { value in
if value != nil {
linkIsActive = true
}
})
.onChange(of: linkIsActive, perform: { value in
if !value {
currentSelection = nil
}
})
//...
}
发生这种情况是因为未呈现较低的项目,因此在层次结构中没有 NavigationLink
带有此类标签
我建议你使用 ZStack
+ EmptyView
NavigationLink
“hack”。
此外,我在这里使用 LazyView,感谢 @autoclosure
,它让我可以通过向上包装 currentSelection
:这只会在 NavigationLink
处于活动状态时调用,并且发生在currentSelection != nil
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
var body: some View {
NavigationView {
ZStack {
EmptyNavigationLink(
destination: { DetailView(entry: [=10=]) },
selection: $currentSelection
)
Form {
ForEach(entries.sorted(), id: \.self) { entry in
NavigationLink(
destination: DetailView(entry: entry),
label: { Text("The number \(entry)") })
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) { Button("Add low") {
let newEntry = (entries.min() ?? 1) - 1
entries.insert(newEntry, at: 1)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) { Button("Add high") {
let newEntry = (entries.max() ?? 50) + 1
entries.append(newEntry)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Text("The current selection is \(String(describing: currentSelection))")
}
}
}
}
}
}
struct DetailView: View {
let entry: Int
var body: some View {
Text("It's a \(entry)!")
}
}
public struct LazyView<Content: View>: View {
private let build: () -> Content
public init(_ build: @autoclosure @escaping () -> Content) {
self.build = build
}
public var body: Content {
build()
}
}
struct EmptyNavigationLink<Destination: View>: View {
let lazyDestination: LazyView<Destination>
let isActive: Binding<Bool>
init<T>(
@ViewBuilder destination: @escaping (T) -> Destination,
selection: Binding<T?>
) {
lazyDestination = LazyView(destination(selection.wrappedValue!))
isActive = .init(
get: { selection.wrappedValue != nil },
set: { isActive in
if !isActive {
selection.wrappedValue = nil
}
}
)
}
var body: some View {
NavigationLink(
destination: lazyDestination,
isActive: isActive,
label: { EmptyView() }
)
}
}
查看有关 LazyView 的更多信息,它通常对 NavigationLink
有帮助:在实际应用程序中,目标可能是一个巨大的屏幕,当每个单元格中都有一个 NavigationLink
时,SwiftUI 将处理所有可能导致滞后的问题
因为可能需要查看表单中添加了哪一行。我想展示一个解决方案,首先滚动到新插入的项目,然后显示详细视图。
虽然这不是我最喜欢的动画或流程。
该解决方案添加了一个额外的 ScrollViewReader,它似乎也可以完美地与 Forms 一起使用。我们要滚动到的视图是 NavigationLink(Label 不起作用,因为它在未显示时未实例化)。
注意
我偶尔会遇到一些故障,可能是Playgrounds。此外,在 Xcode 13 beta 4 上,控制台中发出警告,表明 NavigationLink 存在问题,我们应该提交错误报告。嗯...
我怀疑在执行滚动动画和随后的推送导航时可能存在问题。需要更多调查。
struct ContentView: View {
@State var entries = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
@State var currentSelection: Int? = nil
var body: some View {
NavigationView {
ScrollViewReader { proxy in
Form {
ForEach(entries.sorted(), id: \.self) { entry in
NavigationLink(
destination: DetailView(entry: entry),
tag: entry,
selection: $currentSelection,
label: { Text("The number \(entry)") }
)
.id("-\(entry)-")
}
}
.onChange(of: currentSelection) { newValue in
withAnimation {
if let currentSelection = self.currentSelection {
proxy.scrollTo("-\(currentSelection)-")
}
}
}
.toolbar {
ToolbarItem(placement: ToolbarItemPlacement.navigationBarLeading) { Button("Add low") {
let newEntry = (entries.min() ?? 1) - 1
entries.insert(newEntry, at: 1)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.navigationBarTrailing) { Button("Add high") {
let newEntry = (entries.max() ?? 50) + 1
entries.append(newEntry)
currentSelection = newEntry
} }
ToolbarItem(placement: ToolbarItemPlacement.bottomBar) {
Text("The current selection is \(String(describing: currentSelection))")
}
}
}
}
}
}
struct DetailView: View {
let entry: Int
var body: some View {
Text("It's a \(entry)!")
}
}