You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI层级列表OnTapGesture实现分类与子分类联动显示

SwiftUI层级列表点击时同时获取父分类与子分类信息的解决方案

问题描述

在SwiftUI的层级列表中使用onTapGesture时遇到以下问题:

  • 点击无子类的分类(如Housing),「Selected Category」区域能正确显示该分类的图标和名称;
  • 点击带子类的分类下的子项(如Bills下的Telephone),仅能获取子分类的名称,无法关联父分类的信息。

需要实现的效果:

  • 点击无子类的分类:仅显示该分类的图标和名称;
  • 点击子类:同时显示父分类的图标、名称,以及该子分类的名称。

解决方案

步骤1:定义选中状态的枚举类型

用枚举区分两种选中场景,清晰存储不同类型的分类信息:

enum Selection {
    case standalone(icon: String, name: String)
    case nested(parentIcon: String, parentName: String, childName: String)
}

步骤2:自定义递归层级列表视图

系统自带的层级列表无法直接获取父节点信息,因此需要手动实现递归视图,在遍历子分类时传递父分类的引用:

struct CategoryRow: View {
    let category: Category
    let parentCategory: Category?
    let onSelect: (Category, Category?) -> Void
    
    var body: some View {
        HStack {
            Image(systemName: category.icon)
            Text(category.name)
        }
        .onTapGesture {
            onSelect(category, parentCategory)
        }
        
        // 递归渲染子分类,添加缩进区分层级
        if let subCategories = category.subCategory {
            ForEach(subCategories) { subCat in
                CategoryRow(category: subCat, parentCategory: category, onSelect: onSelect)
                    .padding(.leading, 20)
            }
        }
    }
}

步骤3:更新ContentView的状态与UI

替换原有状态变量,使用自定义递归视图,并在选中时更新对应状态,最后根据状态渲染「Selected Category」区域:

struct ContentView: View {
    
    @State private var items = Category.preview()
    @State private var selection: Selection?
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    List {
                        ForEach(items) { category in
                            CategoryRow(category: category, parentCategory: nil, onSelect: { selectedCat, parent in
                                if let parent = parent {
                                    // 点击子分类,保存父分类与子分类信息
                                    selection = .nested(parentIcon: parent.icon, parentName: parent.name, childName: selectedCat.name)
                                } else {
                                    // 点击独立分类,保存自身信息
                                    selection = .standalone(icon: selectedCat.icon, name: selectedCat.name)
                                }
                            })
                        }
                    }
                } header: {
                    Text("Category List")
                }
                
                Section {
                    HStack(spacing: 8) {
                        switch selection {
                        case .standalone(let icon, let name):
                            Image(systemName: icon)
                            Text(name)
                        case .nested(let parentIcon, let parentName, let childName):
                            Image(systemName: parentIcon)
                            Text(parentName)
                            Text(childName)
                                .foregroundColor(.secondary) // 用颜色区分子分类
                        case nil:
                            Text("请选择一个分类")
                                .foregroundColor(.gray)
                        }
                    }
                } header: {
                    Text("Selected Category")
                }
            }
            .navigationTitle("Category Selector")
        }
    }
}

完整代码

import SwiftUI

struct Category: Hashable, Identifiable {
    var name: String
    let icon: String
    var subCategory: [Category]? = nil
    let id = UUID()

    static func preview() -> [Category] {
        [Category(name: "Bills",
                  icon: "lightbulb",
                  subCategory: [Category(name: "Electricity", icon: "lightbulb"),
                               Category(name: "Telephone", icon: "lightbulb")]),
         Category(name: "Transport",
                  icon: "car",
                  subCategory: [Category(name: "Taxi", icon: "car")]),
         Category(name: "Housing", icon: "house")]
    }
}

enum Selection {
    case standalone(icon: String, name: String)
    case nested(parentIcon: String, parentName: String, childName: String)
}

struct CategoryRow: View {
    let category: Category
    let parentCategory: Category?
    let onSelect: (Category, Category?) -> Void
    
    var body: some View {
        HStack {
            Image(systemName: category.icon)
            Text(category.name)
        }
        .onTapGesture {
            onSelect(category, parentCategory)
        }
        
        if let subCategories = category.subCategory {
            ForEach(subCategories) { subCat in
                CategoryRow(category: subCat, parentCategory: category, onSelect: onSelect)
                    .padding(.leading, 20)
            }
        }
    }
}

struct ContentView: View {
    
    @State private var items = Category.preview()
    @State private var selection: Selection?
    
    var body: some View {
        NavigationView {
            Form {
                Section {
                    List {
                        ForEach(items) { category in
                            CategoryRow(category: category, parentCategory: nil, onSelect: { selectedCat, parent in
                                if let parent = parent {
                                    selection = .nested(parentIcon: parent.icon, parentName: parent.name, childName: selectedCat.name)
                                } else {
                                    selection = .standalone(icon: selectedCat.icon, name: selectedCat.name)
                                }
                            })
                        }
                    }
                } header: {
                    Text("Category List")
                }
                
                Section {
                    HStack(spacing: 8) {
                        switch selection {
                        case .standalone(let icon, let name):
                            Image(systemName: icon)
                            Text(name)
                        case .nested(let parentIcon, let parentName, let childName):
                            Image(systemName: parentIcon)
                            Text(parentName)
                            Text(childName)
                                .foregroundColor(.secondary)
                        case nil:
                            Text("请选择一个分类")
                                .foregroundColor(.gray)
                        }
                    }
                } header: {
                    Text("Selected Category")
                }
            }
            .navigationTitle("Category Selector")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

内容的提问来源于stack exchange,提问作者mzkn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:23:18