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

SwiftUI中层级菜单搜索过滤不全的技术求助

SwiftUI 多层级菜单搜索过滤问题

问题背景

在SwiftUI中实现菜单搜索功能时,无法正确遍历所有层级菜单并过滤出匹配关键词的项。当前代码仅能返回部分结果,需要实现**任意层级的title匹配关键词时,将该菜单加入filteredModuel**的功能。

API响应结构

{"errorCode": 0,"status": "ok","message": "App Layout","menus": [{"menuID": 1,"icon": "https://api/AppIcons/svg/Students.svg","title": "Students","childMenus": [{"chMenuID": 1,"menuID": 0,"title": "Student Profile","icon": "https://api/AppIcons/svg/Students.svg","childMenus": null},{"chMenuID": 41,"menuID": 0,"title": "Update Record","icon": "https://api/AppIcons/svg/Students.svg","childMenus": [{"sbChMenuID": 1,"chMenuID": 0,"title": "Class Promotion","icon": "https://api/AppIcons/svg/Students.svg"}]}]},{"menuID": 2,"title": "Staff","icon": "https://api/AppIcons/svg/Students.svg","childMenus": [{"chMenuID": 4,"menuID": 0,"title": "Staff Profile","icon": "https://api/AppIcons/svg/Students.svg","childMenus": null},{"chMenuID": 6,"menuID": 0,"title": "Staff Leave","icon": "https://api/AppIcons/svg/Students.svg","childMenus": null},{"chMenuID": 62,"menuID": 0,"title": "Staff Attendance","icon": "https://api/AppIcons/svg/Students.svg","childMenus": null}]}]}

需求说明

  • 搜索关键词staf时,期望返回:[Staff Profile、Staff Leave、Staff Attendance]
  • 搜索关键词profil时,期望返回:[Staff Profile、Student Profile]

当前代码

SearchView 实现

struct SearchView: View {
    @State private var searchKeyword: String = ""
    @State private var filteredModuel: [AppMenu] = []

    @State private var selectedIndex: Int? = 4
    @State private var navigateModule: Bool = false
    @State private var moduleId: String = ""

    var body: some View {
        VStack(spacing: 0) {
            // 在TabView中展示filterDataModuel()的结果
            filterDataModuel()
            // 其他代码
        }
    }

    private func filterDataModuel() {
        let lowercasedKeyword = searchKeyword.lowercased()
        filteredModuel = []
        
        if let menus = viewModelSidemenu.layout?.menus {
            for menu in menus {
                let filteredChildMenus = menu.childMenus?.filter { childMenu in
                    let childTitleMatches = childMenu.title?.lowercased().contains(lowercasedKeyword) ?? false
                    return childTitleMatches
                } ?? []
                let titleMatches = menu.title?.lowercased().contains(lowercasedKeyword) ?? false
                
                if !filteredChildMenus.isEmpty {
                    filteredModuel.append(contentsOf: filteredChildMenus)
                }
                if titleMatches && filteredChildMenus.isEmpty {
                    filteredModuel.append(menu)
                }
            }
        }
    }

    @ViewBuilder func moduelView(moduel: AppMenu) -> some View {
        VStack {
            let urlStr = moduel.icon ?? ""
            URLImageView(url: urlStr, placeholder: "NoProfilePic", width: 100, height: 100, renderingMode: .template)
                .foregroundStyle(.black)
                .padding()
            
            Text(moduel.title ?? " ")
                .font(.calibriBold(with: 16))
                .foregroundStyle(Color.hex1E1D0E)
                .multilineTextAlignment(.center)
        }
    }
}

数据模型

// MARK: - AppLayoutModel
struct AppLayoutModel: Codable {
    let status: String?
    let isAuthenticated: Bool?
    let menus: [AppMenu]?
    let errorCode: Int?

    enum CodingKeys: String, CodingKey {
        case status, isAuthenticated, menus, userInfo, errorCode
    }
}

struct AppMenu: Codable, Identifiable {
    let title, icon: String?
    let menuID, chMenuID: Int?
    let slNo: Int?
    var childMenus: [AppMenu]?
    
    var uniqueID: Int {
        return menuID ?? -1
    }
    var id: Int {
        return uniqueID
    }
}

解决方案

当前代码仅遍历了顶层菜单和第一层子菜单,未处理更深层级的子菜单,且逻辑存在匹配规则问题。通过递归遍历所有层级菜单的方式,可以实现任意层级的匹配收集。

修改filterDataModuel函数如下:

private func filterDataModuel() {
    let lowercasedKeyword = searchKeyword.lowercased()
    filteredModuel = []
    
    guard let menus = viewModelSidemenu.layout?.menus else { return }
    
    // 递归遍历所有层级,收集匹配的菜单
    func collectMatchingMenus(from menus: [AppMenu]) -> [AppMenu] {
        var matchedMenus: [AppMenu] = []
        
        for menu in menus {
            // 检查当前菜单标题是否匹配关键词
            if let title = menu.title, title.lowercased().contains(lowercasedKeyword) {
                matchedMenus.append(menu)
            }
            
            // 递归处理子菜单,收集子菜单中的匹配项
            if let childMenus = menu.childMenus {
                matchedMenus.append(contentsOf: collectMatchingMenus(from: childMenus))
            }
        }
        
        return matchedMenus
    }
    
    filteredModuel = collectMatchingMenus(from: menus)
}

代码说明

  1. 定义递归函数collectMatchingMenus,负责遍历传入的菜单数组:
    • 先检查当前菜单的title是否匹配关键词,匹配则加入结果数组
    • 若当前菜单存在子菜单,递归调用自身处理子菜单,并将子菜单的匹配项追加到结果数组
  2. 顶层调用该递归函数,传入所有顶层菜单,最终将所有层级的匹配项赋值给filteredModuel

此方案可以覆盖所有层级的菜单,完全满足需求中的搜索结果要求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 01:22:06