Jak wyświetlać dane hierarchiczne za pomocą SwiftUI 4

Kto z Was nie spotkał się nigdy z problemem wyświetlania hierarchicznych struktur w aplikacji?
SwiftUI oferuje taką możliwość za pomocą zaledwie kilku linii kodu, korzystając z widoków Lists i OutlineGroup . W tym artykule pokażę Ci, jak to zrobić w kilku krokach.
Załóżmy, że musimy wyświetlić informacje hierarchiczne w widoku listy, umożliwiając użytkownikowi ukrywanie i pokazywanie różnych sekcji hierarchii.
Na przykład chcemy nawigować po następującej części systemu plików, która obejmuje katalogi i pliki. Jest to typowe drzewo hierarchiczne.

Następnie uruchom i uruchom Xcode, aby utworzyć nowy projekt aplikacji o nazwie HierarchicalDemo:

Pierwszym krokiem jest stworzenie struktury modelującej węzeł drzewa hierarchicznego. W inspektorze projektu w grupie Model tworzymy plik Swift o nazwie NodeInfo.swift i definiujemy strukturę NodeInfo :

Każdy węzeł ma nazwę, obraz i kolor, które go reprezentują, w zależności od tego, czy jest to katalog, czy plik, może mieć węzły potomne. Struktura jest zgodna z identyfikowalnym protokołem, dzięki czemu każda instancja może być jednoznacznie identyfikowana w widoku listy.
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)
}
}
}
Teraz mamy model dla węzła hierarchii i widok dla wiersza w widoku listy. Kolejnym krokiem jest zdefiniowanie w strukturze ContentView stałej tablicy NodeItems , która implementuje drzewo systemu plików, powyżej:
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)])]
Ponieważ chcemy, aby lista miała własny nagłówek, w ContentView.swift definiujemy strukturę 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)
}
}
Teraz nadszedł czas, aby zbudować naszą hierarchiczną listę. W treści contentView wpisujemy poniższy kod:
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())
}

A to ostateczny wynik:
