SwiftUI 樹形結(jié)構(gòu)展開與收起

11.png

可以控制獨(dú)自的展開與收起智厌,也可以控制全部的展開與收起,全部代碼:

//
//  TreeListView.swift
//  TreeListView
//
//  Created by xuewu wei on 2022/9/4.
//

import SwiftUI

struct TreeListView: View {
    
    @StateObject var vm: NodeManager = NodeManager()
    @State var isExpanded: Bool = false
    
    var body: some View {
        
        VStack {
            List {
                ForEach($vm.list, id: \.self) { $item in
                    NodeGroup(node: item, childKeyPath: \.children, expandList: $vm.expandList) { item in
                        
                        if ((item.children?.isEmpty) != nil) {
                            Group {
                                Image(systemName: "folder.fill")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .foregroundColor(.blue)
                                    .frame(width: 30, height: 30)
                                
                                Text(item.name)
                            }
                        }else {
                            HStack {
                                Image(systemName: "doc.fill")
                                    .resizable()
                                    .aspectRatio(contentMode: .fit)
                                    .foregroundColor(.blue)
                                    .frame(width: 30, height: 30)
                                
                                Text(item.name)
                            }
                        }
                        
                    }
                }
            }
            
            Button("全部展開/收起") {
                self.isExpanded = !self.isExpanded
                self.vm.expandAllOrCloseAll(self.isExpanded)
                print("=====: ",self.isExpanded, self.vm.expandList)
            }
        }
    }
}

struct TreeListView_Previews: PreviewProvider {
    static var previews: some View {
        TreeListView()
    }
}


struct NodeGroup<Node, Content: View>: View where Node: Hashable, Node: Identifiable, Node: CustomStringConvertible{
    @State var node: Node
    let childKeyPath: KeyPath<Node, [Node]?>
    @Binding var expandList: [String:Bool]
    @ViewBuilder let content: (_ item: Node) -> Content
    
    var body: some View {
        if node[keyPath: childKeyPath] != nil {
            let expanded = Binding<Bool>(get: {expandList[node.id as! String] ?? false}, set: {expandList[node.id as! String] = $0})
            DisclosureGroup(
                isExpanded: expanded,
                content: {
                    if expanded.wrappedValue {
                        ForEach(node[keyPath: childKeyPath]!) { childNode in
                            NodeGroup(node: childNode, childKeyPath: childKeyPath, expandList: $expandList) { item in
                                content(item)
                            }
                        }
                    }
                }) {
                    content(node)
                }
        } else {
            content(node)
        }
    }
}


// Mark: 數(shù)據(jù)
struct Node: Identifiable, Hashable, CustomStringConvertible {
    var id: String = UUID().uuidString
    var name: String
    var isExpanded: Bool = false
    var children: [Node]?
    var description: String {
        switch children {
        case nil:
            return "? \(name)"
        case .some(let children):
            return children.isEmpty ? "? \(name)" : "? \(name)"
        }
    }
}

class NodeManager: ObservableObject {
    @Published var list: [Node] = []
    @Published var expandList: [String:Bool] = [:] // 控制展開的字典值集合
    init() {
        self.list = [
            Node(name: "哈哈--1", children: [
                Node(name: "哈哈子集--1"),
                Node(name: "哈哈子集--2", children: [
                    Node(name: "哈哈子集--2--1"),
                    Node(name: "哈哈子集--2--2")
                ]),
                Node(name: "哈哈子集--3", children: [
                    Node(name: "哈哈子集--3--1"),
                    Node(name: "哈哈子集--3--2")
                ])
            ]),
            Node(name: "呵呵--1", children: [
                Node(name: "呵呵子集--1"),
                Node(name: "呵呵子集--2", children: [
                    Node(name: "呵呵子集--2--1"),
                    Node(name: "呵呵子集--2--2")
                ]),
            ])
        ]
        
        self.expandList = getExpandArr(arr: self.list)
        
    }
    
    // 根據(jù)數(shù)據(jù)抽出一個控制列表展開與收起的字典
    func getExpandArr(arr: [Node?]) -> [String:Bool] {
        
        var totals: [String:Bool] = [:]
        
        for item in arr {
            if let obj = item {
                // 保留舊鍵
                totals.merge([obj.id: false]) { current, _ in
                    current
                }
                if ((obj.children?.isEmpty) != nil) {
                    totals.merge(getExpandArr(arr: obj.children!)) { current, _ in
                        current
                    }
                }
            }
        }
        
        return totals
    }
    
    // 全部展開或者收起
    func expandAllOrCloseAll(_ flag: Bool) {
        for key in self.expandList.keys {
            self.expandList.updateValue(flag, forKey: key)
        }
    }
}

重點(diǎn)有三:

  1. 一個控制展開與收起的[String:Bool]字典 expandList 集合
  2. 此行代碼
let expanded = Binding<Bool>(get: {expandList[node.id as! String] ?? false}, set: {expandList[node.id as! String] = $0})

DisclosureGroup組件的isExpanded屬性是Binding<Bool>類>型就缆,所以需要把控制展開與收起集合里字典對象包裝一下,以符合組件需要竭贩。

  1. 顯而易見就是樹形的遞歸調(diào)用了

中文資源太少了旧困,大家努力豐富啊Q唷U独!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末从橘,一起剝皮案震驚了整個濱河市念赶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恰力,老刑警劉巖叉谜,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異踩萎,居然都是意外死亡停局,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門香府,熙熙樓的掌柜王于貴愁眉苦臉地迎上來董栽,“玉大人,你說我怎么就攤上這事企孩《迹” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵柠硕,是天一觀的道長工禾。 經(jīng)常有香客問我运提,道長,這世上最難降的妖魔是什么闻葵? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任民泵,我火速辦了婚禮,結(jié)果婚禮上槽畔,老公的妹妹穿的比我還像新娘栈妆。我一直安慰自己,他們只是感情好厢钧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布鳞尔。 她就那樣靜靜地躺著,像睡著了一般早直。 火紅的嫁衣襯著肌膚如雪寥假。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天霞扬,我揣著相機(jī)與錄音糕韧,去河邊找鬼。 笑死喻圃,一個胖子當(dāng)著我的面吹牛萤彩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播斧拍,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼雀扶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了肆汹?” 一聲冷哼從身側(cè)響起愚墓,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昂勉,沒想到半個月后转绷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡硼啤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了斧账。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谴返。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咧织,靈堂內(nèi)的尸體忽然破棺而出嗓袱,到底是詐尸還是另有隱情,我是刑警寧澤习绢,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布渠抹,位于F島的核電站蝙昙,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏梧却。R本人自食惡果不足惜奇颠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望放航。 院中可真熱鬧烈拒,春花似錦、人聲如沸广鳍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赊时。三九已至吨铸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祖秒,已是汗流浹背诞吱。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留狈涮,地道東北人狐胎。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像歌馍,于是被迫代替她去往敵國和親握巢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容