SwiftUI: как получить выбор из OutlineGroup?
OutlineGroup
является аналогом NSOutlineView
. NSOutlineView
поддерживает выбор одного / нескольких узлов, и мы можем получить их, запросив NSOutlineView
. Хотя получение отбора наNSOutlineView
равно O(n), но его можно оптимизировать до O(1), если представление отслеживает выбор и предоставляет их в соответствующем интерфейсе.
Как получить выбор из OutlineGroup
? Специально для выбора нескольких узлов. Я проверил ввод вручную, но не нашел упоминания о выборе. Что мне здесь не хватает?
4 ответа
import SwiftUI
struct ContentView: View {
@State var selection = Set<FileItem.ID>()
var body: some View {
NavigationView {
VStack {
List(selection: $selection) {
OutlineGroup(data, children: \.children) { item in
Text("\(item.description)")
}
.onTapGesture {
print(selection)
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Files")
//.toolbar { EditButton() }
.environment(\.editMode, .constant(.active))
.onTapGesture {
// Walkaround: try how it works without `asyncAfter()`
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: {
print(selection)
})
}
Text("\(selection.count) selections")
}
}
}
}
// Sample data:
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var header: String?
var children: [FileItem]? = nil
var description: String {
switch children {
case nil:
return "📄 \(name)"
case .some(let children):
return children.isEmpty ? "📂 \(name)" : "📁 \(name)"
}
}
}
let data =
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name: "Photos", header: "Header 1", children:
[FileItem(name: "photo001.jpg", header: "Header 2"),
FileItem(name: "photo002.jpg")]),
FileItem(name: "Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name: "Documents", children: [])
]),
FileItem(name: "newuser", children:
[FileItem(name: "Documents", children: [])
])
])
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Вам нужно поместить NavigationLink/Button для элемента, у которого нет дочерних элементов.
Вот как это могло бы выглядеть на основе исходного кода Apple.
var body: some View {
OutlineGroup(data, children: \.children) { item in
Group {
if item.children == nil {
NavigationLink(
destination: Text("\(item.name)"),
label: {
Text ("\(item.description)")
})
} else {
Text ("\(item.description)")
}
}
}
}
Данные взяты из примера Apple. Иногда ссылки не работают. Итак, исходный код:
struct FileItem: Hashable, Identifiable, CustomStringConvertible {
var id: Self { self }
var name: String
var children: [FileItem]? = nil
var description: String {
switch (children) {
case nil:
return " \(name)"
case .some(let children):
return children.count > 0 ? " \(name)" : " \(name)"
}
}
}
let data =
FileItem(name: "users", children:
[FileItem(name: "user1234", children:
[FileItem(name:"Photos", children:
[FileItem(name: "photo001.jpg"),
FileItem(name: "photo002.jpg")]),
FileItem(name:"Movies", children:
[FileItem(name: "movie001.mp4")]),
FileItem(name:"Documents", children: [])
]),
FileItem(name: "newuser", children:
[FileItem (name: "Documents", children: [])
])
])
Документация не доработана, как кажется. Используйте прямые автоматически сгенерированные интерфейсы SwiftUI в Xcode 12 для поиска обновлений.
Специально для спросили OutlineGroup
есть несколько конструкторов с selection
параметр, как показано ниже:
/// Creates a hierarchical list that computes its rows on demand from an
/// underlying collection of identifiable data, optionally allowing users to
/// select multiple rows.
///
/// - Parameters:
/// - data: The identifiable data for computing the list.
/// - selection: A binding to a set that identifies selected rows.
/// - rowContent: A view builder that creates the view for a single row of
/// the list.
@available(iOS 14.0, OSX 10.16, *)
@available(tvOS, unavailable)
@available(watchOS, unavailable)
public init<Data, RowContent>(_ data: Data, children: KeyPath<Data.Element, Data?>,
selection: Binding<Set<SelectionValue>>?, @ViewBuilder rowContent: @escaping (Data.Element) -> RowContent) where Content == OutlineGroup<Data, Data.Element.ID, HStack<RowContent>, HStack<RowContent>, DisclosureGroup<HStack<RowContent>, OutlineSubgroupChildren>>, Data : RandomAccessCollection, RowContent : View, Data.Element : Identifiable
Использование NavigationLink в качестве представления для представления дочерних элементов дерева действительно работает.