arraysswiftswiftuisearchfilter

Unable to get filter data in hierarchal way while searching a word in SwiftUI


This is my API response structure in postman

{
"errorCode": 0,
"status": "ok",
"message": "App Layout",

"menus": [
   {
       "menuID": 1,
        "icon": "https://api/AppIcons/svg/Students.svg
       "title": "Students", //------------------------------------------Top menu
       "childMenus": [//---------------------------- Menu's > childMenu
           {
               "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": [//---------------- Menu's > childMenu > subChildMenu
                   {
                       "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
            },

Code: here i need if title matches in any hierarchy i need that menu in filteredModuel but with my code only any one coming.

1)in above response if i search for keyword "staf"

then i need [staff profile, staff leave, staff attendance]

2)if i search for "profil"

then i need [staff profile, student profile]

how to get this kind of filter... please guide me

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) {
            // in tabview i will show filterDataModuel()
            filterDataModuel()
            // some other code
       
        }
}


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

model for this above response

// 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,
    case 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
}
}

Solution

  • As I see it your filter function can be simplified to

    private func filterDataModuel() {
        filteredModuel = []
    
        guard let menus = viewModelSidemenu.layout?.menus else { return }
    
        for menu in menus {
            var result = menu.childMenus?.filter { $0.menuTitle.localizedCaseInsensitiveContains(searchKeyword) } ?? []
    
            if result.isEmpty, menu.menuTitle.localizedCaseInsensitiveContains(searchKeyword) {
                result.append(menu)
            }
            filteredModuel.append(contentsOf: result)
        }
    }
    

    That is first filter the child menus on the search word and if nothing was found then filter on the title of the current menu object.

    To simplify the filtering code somewhat I added a computed property to AppMenu

    var menuTitle: String {
        return title ?? ""
    }
    

    Another option here is to move the function to the view model instead and slightly change it so it returns the result

    func filterDataModuel(searchKeyword: String) -> [AppMenu]