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

Nov 29 2022
Кто из вас никогда не сталкивался с проблемой отображения иерархических структур в приложении? SwiftUI предлагает эту возможность всего несколькими строками кода, используя представления Lists и OutlineGroup. В этой статье я покажу вам, как это сделать за несколько шагов.
Изображение Герда Альтманна, Pixabay

Кто из вас никогда не сталкивался с проблемой отображения иерархических структур в приложении?

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())
    }

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