微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

催化剂 NavigationView 上的列表滚动冻结

如何解决催化剂 NavigationView 上的列表滚动冻结

我在 macCatalyst 上遇到了 NavigationView 的奇怪问题。下面是一个带有侧边栏和详细信息视图的简单应用程序。选择侧边栏上的项目会显示带有可滚动列表的详细信息视图。

一个 NavigationLink 一切正常,详细视图显示并可自由滚动。但是,如果我选择一个列表项来触发指向第二个详细信息视图的链接,则滚动开始,然后冻结。该应用程序仍然有效,只是详细视图滚动被锁定。

相同的代码在 iPad 上运行良好,没有任何冻结。如果我为 macOS 构建,详细视图中的 NavigationLink 将不起作用。

是否有任何已知的解决方法

这是它的样子,点击 LinkedView 后,短暂滚动,然后视图冻结。仍然可以点击侧边栏上的后退按钮或其他项目,但列表视图被阻止。

enter image description here

代码如下: ContentView.swift

import SwiftUI

struct ContentView: View {

    var names = [NamedItem(name: "One"),NamedItem(name: "Two"),NamedItem(name:"Three")]

    var body: some View {
        NavigationView {
            List() {
                ForEach(names.sorted(by: {$0.name < $1.name})) { item in
                    NavigationLink(destination: DetailListView(item: item)) {
                        Text(item.name)
                    }
                }
            }
            .listStyle(SidebarListStyle())

            Text("Detail view")
        }
    }
}

struct NamedItem: Identifiable {
    let name: String
    let id = UUID()
}

struct DetailListView: View {

    var item: NamedItem

    let sections = (0...4).map({NamedItem(name: "\($0)")})

    var body: some View {
        vstack {
            List {
                Text(item.name)
                NavigationLink(destination: DetailListView(item: NamedItem(name: "LinkedView"))) {
                    listItem("  LinkedView","Item")
                        .foregroundColor(Color.blue)
                }

                ForEach(sections) { section in
                    sectionDetails(section)
                }
            }
        }
    }

    let info = (0...12).map({NamedItem(name: "\($0)")})

    func sectionDetails(_ section: NamedItem) -> some View {
        Section(header: Text("Section \(section.name)")) {
            Group {
                listItem("ID","\(section.id)")
            }
            Text("")
            ForEach(info) { ch in
                listItem("Item \(ch.name)","\(ch.id)")
            }
        }
    }

    func listItem(_ title: String,_ value: String,tooltip: String? = nil) -> some View {
        HStack {
            Text(title)
                .frame(width: 200,alignment: .leading)
            Text(value)
                .padding(.leading,10)
        }
    }

}

TestListApp.swift

import SwiftUI

@main
struct TestListApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

解决方法

我在使用 Mac Catalyst 应用时遇到了同样的问题。在真实设备(带有 iOS 14.4.2 的 iPhone 7)上没有问题,但是对于 Mac Catalyst(带有 Big Sur 11.2.3 的 MacBook Pro),导航视图中的滚动非常随机,如您所解释的那样。我发现问题出在 Macbook 的触控板上,并且与滚动指示器有关,因为使用外部鼠标时不存在此问题。所以解决这个问题最简单的方法是在导航视图中隐藏垂直滚动指示器。至少它对我有用。下面是来自根视图“ContentView”的一些代码,我是如何做到的。不幸的是,大数据丢失了滚动指示器,但至少滚动有效。

import SwiftUI

struct TestView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: NewView()) {
                    Text("Navigation Link to new view")
                }
            }
            .onAppear {
                UITableView.appearance().showsVerticalScrollIndicator = false
            }   
        }
    }
}
,

好的,所以我设法找到了一个解决方法,所以我想我会发布这个以寻求帮助,直到似乎是 macCatalyst SwiftUI 的错误得到修复。我已经发布了一个针对列表冻结问题的雷达:FB8994665

解决方法是仅将 NavigationLink 用于可以导航的一系列页面的第一级(它为我提供侧边栏和工具栏),然后从那时起使用 NavigationStack package管理指向其他页面的链接。

在这种安排下,我遇到了其他几个问题。

首先,NavigationView 工具栏在滚动链接列表视图时会失去背景(除非窗口散焦并重新聚焦),这似乎是另一个催化剂 SwiftUI 错误。我通过设置工具栏背景颜色解决了这个问题。

第二个问题是,在 macCatalyst 下,NavigationStack 的 PushView 标签中使用的 onTouch 视图修饰符不适用于大多数单击。它只会持续触发双击。我通过使用按钮替换标签来解决这个问题。

这是代码,不再有列表冻结!

import SwiftUI
import NavigationStack

struct ContentView: View {

    var names = [NamedItem(name: "One"),NamedItem(name: "Two"),NamedItem(name:"Three")]

    @State private var isSelected: UUID? = nil

    init() {
        // Ensure toolbar is allways opaque
        UINavigationBar.appearance().backgroundColor = UIColor.secondarySystemBackground
    }

    var body: some View {
        NavigationView {
            List {
                ForEach(names.sorted(by: {$0.name < $1.name})) { item in
                    NavigationLink(destination: DetailStackView(item: item)) {
                        Text(item.name)
                    }
                }
            }
            .listStyle(SidebarListStyle())

            Text("Detail view")
                .frame(maxWidth: .infinity,maxHeight: .infinity)
                .toolbar { Spacer() }
        }
    }
}

struct NamedItem: Identifiable {
    let name: String
    let id = UUID()
}

// Embed the list view in a NavigationStackView
struct DetailStackView: View {
    var item: NamedItem

    var body: some View {
        NavigationStackView {
            DetailListView(item: item)
        }
    }
}

struct DetailListView: View {

    var item: NamedItem

    let sections = (0...10).map({NamedItem(name: "\($0)")})

    var linked = NamedItem(name: "LinkedView")

    // Use a Navigation Stack instead of a NavigationLink
    @State private var isSelected: UUID? = nil
    @EnvironmentObject private var navigationStack: NavigationStack

    var body: some View {
        List {
            Text(item.name)
            PushView(destination: linkedDetailView,tag: linked.id,selection: $isSelected) {
                listLinkedItem("  LinkedView","Item")
            }

            ForEach(sections) { section in
                if section.name != "0" {
                    sectionDetails(section)
                }
            }
        }
        .navigationBarTitleDisplayMode(.inline)
        .navigationTitle(item.name)
    }

    // Ensure that the linked view has a toolbar button to return to this view
    var linkedDetailView: some View {
        DetailListView(item: linked)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button(action: {
                        self.navigationStack.pop()
                    },label: {
                        Image(systemName: "chevron.left")
                    })
                }
            }
    }

    let info = (0...12).map({NamedItem(name: "\($0)")})

    func sectionDetails(_ section: NamedItem) -> some View {
        Section(header: Text("Section \(section.name)")) {
            Group {
                listItem("ID","\(section.id)")
            }
            Text("")
            ForEach(info) { ch in
                listItem("Item \(ch.name)","\(ch.id)")
            }
        }
    }

    // Use a button to select the linked view with a single click
    func listLinkedItem(_ title: String,_ value: String,tooltip: String? = nil) -> some View {
        HStack {
            Button(title,action: {
                self.isSelected = linked.id
            })
            .foregroundColor(Color.blue)
            Text(value)
                .padding(.leading,10)
        }
    }

    func listItem(_ title: String,tooltip: String? = nil) -> some View {
        HStack {
            Text(title)
                .frame(width: 200,alignment: .leading)
            Text(value)
                .padding(.leading,10)
        }
    }

}
,

我继续试验 NavigationStack 并进行了一些修改,使其能够直接换入和换出 List 行。这避免了我在 NavigationBar 背景中看到的问题。导航栏设置在 NavigationStackView 之上的级别,对标题的更改通过 PreferenceKey 传递。如果堆栈为空,导航栏上的后退按钮将隐藏。

以下代码使用了 PR#44swiftui-navigation-stack

import SwiftUI

struct ContentView: View {

    var names = [NamedItem(name: "One"),NamedItem(name:"Three")]

    @State private var isSelected: UUID? = nil

    var body: some View {
        NavigationView {
            List {
                ForEach(names.sorted(by: {$0.name < $1.name})) { item in
                    NavigationLink(destination: DetailStackView(item: item)) {
                        Text(item.name)
                    }
                }
            }
            .listStyle(SidebarListStyle())

            Text("Detail view")
                .frame(maxWidth: .infinity,maxHeight: .infinity)
                .toolbar { Spacer() }
        }
    }
}

struct NamedItem: Identifiable {
    let name: String
    let depth: Int
    let id = UUID()
    init(name:String,depth: Int = 0) {
        self.name = name
        self.depth = depth
    }
    var linked: NamedItem {
        return NamedItem(name: "Linked \(depth+1)",depth:depth+1)
    }
}

// Preference Key to send title back down to DetailStackView
struct ListTitleKey: PreferenceKey {
    static var defaultValue: String = ""

    static func reduce(value: inout String,nextValue: () -> String) {
        value = nextValue()
    }
}

extension View {
    func listTitle(_ title: String) -> some View {
        self.preference(key: ListTitleKey.self,value: title)
    }
}

// Embed the list view in a NavigationStackView
struct DetailStackView: View {
    var item: NamedItem

    @ObservedObject var navigationStack = NavigationStack()

    @State var toolbarTitle: String = ""

    var body: some View {
        List {
            NavigationStackView(noGroup: true,navigationStack: navigationStack) {
                DetailListView(item: item,linked: item.linked)
                    .listTitle(item.name)
            }
        }
        .listStyle(PlainListStyle())
        .animation(nil)

        // Updated title
        .onPreferenceChange(ListTitleKey.self) { value in
            toolbarTitle = value
        }
        .navigationBarTitleDisplayMode(.inline)
        .navigationTitle("\(toolbarTitle) \(self.navigationStack.depth)")
        .toolbar(content: {
            ToolbarItem(id: "BackB",placement: .navigationBarLeading,showsByDefault: self.navigationStack.depth > 0) {
                Button(action: {
                    self.navigationStack.pop()
                },label: {
                    Image(systemName: "chevron.left")
                })
                .opacity(self.navigationStack.depth > 0  ? 1.0 : 0.0)
            }
        })
    }
}

struct DetailListView: View {

    var item: NamedItem
    var linked: NamedItem

    let sections = (0...10).map({NamedItem(name: "\($0)")})

    // Use a Navigation Stack instead of a NavigationLink
    @State private var isSelected: UUID? = nil
    @EnvironmentObject private var navigationStack: NavigationStack

    var body: some View {
        Text(item.name)
        PushView(destination: linkedDetailView,selection: $isSelected) {
            listLinkedItem("  LinkedView","Item")
        }

        ForEach(sections) { section in
            if section.name != "0" {
                sectionDetails(section)
            }
        }
    }

    // Ensure that the linked view has a toolbar button to return to this view
    var linkedDetailView: some View {
        DetailListView(item: linked,linked: linked.linked)
            .listTitle(linked.name)
    }

    let info = (0...12).map({NamedItem(name: "\($0)")})

    func sectionDetails(_ section: NamedItem) -> some View {
        Section(header: Text("Section \(section.name)")) {
            Group {
                listItem("ID","\(ch.id)")
            }
        }
    }

    func buttonAction() {
        self.isSelected = linked.id
    }

    // Use a button to select the linked view with a single click
    func listLinkedItem(_ title: String,action: buttonAction)
                .foregroundColor(Color.blue)
            Text(value)
                .padding(.leading,10)
        }
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。