SwiftUI 递归列表上没有上下文菜单

问题描述 投票:0回答:1

我正在尝试将用 Java 编写的程序移植到 SwiftUI。 UI 的基本结构是主视图左侧的导航树。用户可以选择一项,多项详细信息将显示在右侧的表格中。 导航树中的节点允许不同的操作 - 取决于它们的类型。 这些操作应该作为上下文菜单来实现。 Screen shot

我已经实现了一个小示例,但我无法使上下文菜单工作。

我想我使用了错误的方法。这是我的示例代码:

import SwiftUI

struct ContentView: View {

  @State var selection = Set<Tree<String>>()

  var body: some View {
    NavigationView {
      List(treeNodes, id: \.value, children: \.children, selection: $selection) { tree in
        NavigationLink {
          TabView {
            Tab("Details 1", image: "Block") {
              Text(tree.value)
            }
            Tab("Details 2", image: "Haus") {
              Text(tree.value)
            }
          }
        } label: {
          Label(tree.value, image: tree.icon!)
        }
      }
      .contextMenu(forSelectionType: Tree<String>.self) { nodes in
         if nodes.count == 1 {
          Button("One node selected") {}
        } else {
          Button("No node or more than 1 selected") {}
        }
      }
      .listStyle(SidebarListStyle())
    }
  }
}

#Preview {
  ContentView()
}

用于完成代码

import SwiftUI

// MARK: - Tree
/// Class for arbitrary trees. Values must be hashable
class Tree<Value: Hashable>: Hashable {

  var value: Value
  var icon: String?
  var children: [Tree]?

  init(value: any Hashable, icon: String? = nil, children: [Tree]? = nil) {
    // swiftlint: disable force_cast
    self.value = value as! Value
    // swiftlint: enable force_cast
    self.icon = icon
    self.children = children
  }

  static func == (lhs: Tree<Value>, rhs: Tree<Value>) -> Bool {
    return lhs.value == rhs.value
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(value)
  }
}

// MARK: - Value
/// Class for the stored value
class Value: Hashable, Equatable {
  var id = UUID()

  func hash(into hasher: inout Hasher) {
    hasher.combine(id)
  }

  static func == (lhs: Value, rhs: Value) -> Bool {
    return lhs.id == rhs.id
  }
}

// MARK: - init demo data
/// example data using string values
let treeNodes: [Tree<String>] = [
  .init(
    value: "Root", icon: "Block",
    children: [
      .init(value: "House 1", icon: "Haus"),
      .init(value: "House 2", icon: "Haus"),
      .init(
        value: "House 3", icon: "Haus",
        children: [
          .init(value: "Apartment 1", icon: "Wohnung"),
          .init(value: "Apartment 2", icon: "Wohnung")
        ]
      )
    ]
  )
]
swiftui contextmenu swiftui-list swiftui-navigationview
1个回答
0
投票

您不应将新 API 与旧 API 混合使用。使用

NavigationSplitView
而不是
NavigationView
。之后,代码将按预期工作。

NavigationSplitView {
    List(treeNodes, id: \.value, children: \.children, selection: $selection) { tree in
        NavigationLink(value: tree) {
            // why make icon optional when you are going to force unwrap it?
            Label(tree.value, image: tree.icon!)
        }
    }
    .contextMenu(forSelectionType: Tree<String>.self) { nodes in
        if nodes.count == 1 {
            Button("One node selected") {}
        } else {
            Button("No node or more than 1 selected") {}
        }
    }
    .listStyle(.sidebar)
} detail: {
    // I'm not sure if TabViews in a detail view is officially supported.
    // consider using a segmented Picker to act as tabs instead.
    TabView {
        // it is unclear what detail view you want to show when multiple items are selected, 
        // so here I have just used a random one from the set
        if let tab = selection.first {
            Tab("Details 1", image: "Block") {
                Text(tab.value)
            }
            Tab("Details 2", image: "Haus") {
                Text(tab.value)
            }
        } else {
            Tab {
                Text("No selection")
            }
        }
    }
}

还可以考虑将

Tree
更改为
struct
,因为它们在 SwiftUI 中更容易使用。你只需要:

struct Tree<Value: Hashable>: Hashable {
    var value: Value
    var icon: String?
    var children: [Tree]?
}

如果你真的希望

Tree
成为一个类,你应该将其设为
@Observable
,否则 SwiftUI 将无法看到
value
icon
children
的变化来更新视图。

我不确定

Value
类想要实现什么目标。

© www.soinside.com 2019 - 2024. All rights reserved.