【发布时间】:2021-11-29 05:09:27
【问题描述】:
这个问题是在我昨天问过(@Asperi 已经回答)的this question 之后提出的,但它引入了一个新的意想不到的元素。
基本设置是一个 3 列的 macOS SwiftUI 应用程序。如果您运行下面的代码并将列表滚动到列表下方的某个项目(例如第 80 项)并单击,List 将重新渲染并偶尔“跳转”到某个位置(例如第 40 项),留下实际的所选项目超出框架。上一题通过将SidebarRowView封装到自己的视图中解决了这个问题。
但是,如果将活动绑定 (activeItem) 存储为 @State 变量在 SidebarList 视图上(请参阅我标记 //#1 的位置),则该解决方案有效。如果活动项存储在ObservableObject 视图模型上(请参阅//#2),则滚动行为会受到影响。
我认为这是因为差异算法在某种程度上与@Published 值和@State 值的工作方式不同。 我想找到一种使用@Published 值的方法,因为活动项目需要通过应用程序的状态进行操作并通过isActive: 在NavigationLink 中使用(例如,如果推送通知进来影响它)。
有没有办法使用@Published 值而不让它重新渲染整个List 从而不影响滚动位置?
以下是可重现的代码 - 请参阅注释行了解更改内容以查看 @Published 与 @State 的行为
struct Item : Identifiable, Hashable {
let id = UUID()
var name : String
}
class SidebarListViewModel : ObservableObject {
@Published var items = Array(0...300).map { Item(name: "Item \($0)") }
@Published var activeItem : Item? //#2
}
struct SidebarList : View {
@StateObject private var viewModel = SidebarListViewModel()
@State private var activeItem : Item? //#1
var body: some View {
List(viewModel.items) {
SidebarRowView(item: $0, activeItem: $viewModel.activeItem) //change this to $activeItem and the scrolling works as expected
}.listStyle(SidebarListStyle())
}
}
struct SidebarRowView: View {
let item: Item
@Binding var activeItem: Item?
func navigationBindingForItem(item: Item) -> Binding<Bool> {
.init {
activeItem == item
} set: { newValue in
if newValue {
activeItem = item
}
}
}
var body: some View {
NavigationLink(destination: Text(item.name),
isActive: navigationBindingForItem(item: item)) {
Text(item.name)
}
}
}
struct ContentView : View {
var body: some View {
NavigationView {
SidebarList()
Text("No selection")
Text("No selection")
.frame(minWidth: 300)
}
}
}
(在 macOS 11.3 上使用 Xcode 13.0 构建和测试)
【问题讨论】:
-
对于最小的、可重现的例子来说,这似乎是一个非常复杂的解决方案。感觉就像是掉进了兔子洞。我想获得更好的视角。使用您的应用程序,您可以从第一列中选择一个项目。这是否会导致第二列填充基于第一列选择的选项?然后是第三列的详细信息?还是前两列是独立的,并且组合可以为您提供第三列的详细信息?
-
@Yrb 我恭敬地不同意它“非常复杂”——它不到 50 行代码,而 一行 更改会极大地影响行为。是的,在我的实际应用程序中,第二列基于第一列的选择,详细信息在第三列 - ala 邮件应用程序。当我测试它时,所有这些代码都被删除了,它与第一列中的行为无关。有可能我可以删除示例中的第三列,但这似乎微不足道,因为它只是一个
Text项目。 -
@Cristik 但在
NavigationView根目录中没有 3 个子节点,因此无法获得 3 列布局。有没有其他方法可以得到它?如果不是,我认为我“没有正确使用导航视图”——事实上,这似乎是实现这种布局的标准做法。 -
对此进行了更多测试,如果删除传递给
NavigationLink的isActive参数,问题就会消失。你需要那个吗,顺便说一句?从架构上讲,列表行知道周围的上下文是很奇怪的,比如父列表中的选定项。 -
我确实需要 isActive - 例如,如果推送通知进来,我会影响导航,这将保证显示特定视图。