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 в качестве представления для представления дочерних элементов дерева действительно работает.

Другие вопросы по тегам