首页 > 解决方案 > 如果未显示所有 NavigationLink,则 NavigationView 中 SwiftUI 的 NavigationLink `tag` 和 `selection` 将停止工作

问题描述

我在 aForm中有一个项目列表NavigationView,每个项目都有一个可以通过NavigationLink. 当我向列表中添加一个新元素时,我想显示它的详细视图。为此,我使用接收为的 a @State var currentSelection,并且每个元素都具有以下功能:NavigationLinkselectiontag

NavigationLink(
  destination: DetailView(entry: entry),
  tag: entry,
  selection: $currentSelection,
  label: { Text("The number \(entry)") })

这很有效,它遵循Apple 文档最佳实践

令人惊讶的是,当列表中的元素多于屏幕(加上〜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个项目并在标签上设置填充来排除元素数量是核心问题label: { Text("The number \(entry).padding(30)") }):)

正如您在屏幕记录中看到的那样,在达到关键元素数量后(通过预先或附加到列表),底部工作表仍然显示currentSelection正在更新,但没有发生导航。

我使用了 iOS 14.7.1、Xcode 12.5.1 和 Swift 5。

添加到列表的开头 添加到列表的末尾

标签: iosswiftuiswiftui-navigationlinkswiftui-navigationview

解决方案


发生这种情况是因为没有渲染较低的项目,所以在层次结构中没有NavigationLink这样的标签

我建议您使用ZStack+ EmptyView NavigationLink“hack”。

我也在这里使用 LazyView ,多亏了@autoclosure它让我通过 upwrapped 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: $0) },
                    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 将处理所有这些可能导致滞后


推荐阅读