Как отображать иерархические данные с помощью SwiftUI 4

Кто из вас никогда не сталкивался с проблемой отображения иерархических структур в приложении?
SwiftUI предлагает эту возможность всего несколькими строками кода, используя представления Lists и OutlineGroup . В этой статье я покажу вам, как это сделать за несколько шагов.
Предположим, нам нужно отобразить иерархическую информацию в представлении списка, позволяя пользователю скрывать и отображать различные разделы иерархии.
Например, мы хотим перемещаться по следующей части файловой системы, которая включает в себя каталоги и файлы. Это типичное иерархическое дерево.

Затем запустите Xcode, чтобы создать новый проект приложения с именем HierarchicalDemo:

Первым шагом является создание структуры, моделирующей узел иерархического дерева. В инспекторе проекта в группе Model мы создаем файл Swift с именем NodeInfo.swift и определяем структуру NodeInfo :

Каждый узел имеет имя, изображение и цвет, которые представляют его в зависимости от того, является ли он каталогом или файлом, он может иметь дочерние узлы. Структура соответствует протоколу идентификации , поэтому каждый экземпляр может быть однозначно идентифицирован в представлении списка.
struct NodeInfo: Identifiable{
var id = UUID()
var name: String
var image: String
var color: Color
var children: [NodeInfo]?
}
struct NodeCell: View {
var nodeItem: NodeInfo
var body: some View {
HStack{
Image(systemName: nodeItem.image)
.resizable()
.scaledToFit()
.frame(width: 25,height: 25)
.foregroundColor(nodeItem.color)
Text(nodeItem.name)
}
}
}
Теперь у нас есть модель для узла иерархии и представление для строки в представлении списка. Следующим шагом является определение в структуре ContentView константного массива NodeItems , реализующего дерево файловой системы, как показано выше:
let nodeItems: [NodeInfo] =
[NodeInfo(name: "ROOT", image: "folder",color: .orange, children:
[NodeInfo(name:"Documents", image: "folder.circle",color: .blue, children:[NodeInfo(name: "Accounting", image: "folder.circle",color: .blue, children: [NodeInfo(name: "rental.doc", image: "doc.circle",color: .cyan),NodeInfo(name: "invoice1.doc", image: "doc.circle", color: .cyan),NodeInfo(name: "invoice2.doc", image: "doc.circle",color: .cyan)])]),
NodeInfo(name: "Photo", image: "folder.circle",color: .blue, children: [NodeInfo(name: "Selfie", image: "folder.circle",color:.blue,children: [NodeInfo(name: "john.png", image: "photo.circle",color: .green),NodeInfo(name: "Julie.png", image: "photo.circle",color: .green)]),NodeInfo(name: "mybirthday.png", image: "photo.circle",color: .green)]),
NodeInfo(name: "Video", image: "folder.circle",color:.blue, children: [NodeInfo(name: "Holiday", image: "folder.circle",color:.blue,children: [NodeInfo(name: "mountins.wav", image: "video.circle",color: .orange),NodeInfo(name: "sea.wav", image: "video.circle",color:.orange)]),NodeInfo(name: "birthday.mp3", image: "video.circle.fill",color:.orange),NodeInfo(name: "cat.wav", image: "video.circle",color:.orange)]),
NodeInfo(name: "booking.pdf", image: "doc.circle",color: .green)])]
Поскольку мы хотим, чтобы у списка был собственный заголовок, в ContentView.swift мы определяем структуру SectionHeader :
struct SectionHeader: View{
var nodeItem: NodeInfo
var body: some View{
HStack{
Image(systemName: nodeItem.image)
.resizable()
.scaledToFit()
.frame(width: 25, height: 25)
Text(nodeItem.name)
.font(.title2.bold())
}
.foregroundColor(nodeItem.color)
}
}
Теперь пришло время построить наш иерархический список. В теле contentView мы вводим следующий код:
var body: some View {
List{
ForEach(nodeItems) { item in
Section(header: SectionHeader(nodeItem: item)){
OutlineGroup(item.children ?? [NodeInfo](), children: \.children){child in
NodeCell(nodeItem: child)
}
}
}
}
.listStyle(InsetListStyle())
}

И это окончательный результат:
