redearz
redearz

Reputation: 135

SwiftUI search list from JSON file

Currently my SearchView uses a simple filter on array names and I need it to make it search trough my JSON file. I have a JSON file with this structure:

struct UcmData: Codable, Identifiable {
    let id: Int
    let building: [Building]
}

// MARK: - Building
struct Building: Codable, Identifiable {
    let id: Int
    let title, subtitle, info, image: String
    let floor: [Floor]
}

// MARK: - Floor
struct Floor: Codable, Identifiable {
    let id, number: Int
    let title, subtitle, image: String
    let cabinet: [Cabinet]?
}

// MARK: - Cabinet
struct Cabinet: Codable, Identifiable {
    let id: Int
    let number: String
    let person: [Person]
}

// MARK: - Person
struct Person: Codable, Identifiable {
    let id: Int
    let name: String
}

SearchView:

struct SearchView: View {

    let names = ["306 B", "doc. Ing. Michal Čerňanský, PhD."]
    let ucmData = Bundle.main.decode(UcmData.self, from: "ucm_data.json")
    @State private var searchText = ""
    @State private var showCancelButton: Bool = false


    var body: some View {
        VStack {
            HStack {
                HStack {
                    Image(systemName: "magnifyingglass")
                    TextField("Zadajte text pre vyhľadávanie", text: $searchText, onEditingChanged: { isEditing in
                        self.showCancelButton = true
                    }, onCommit: {
                        print("onCommit")
                    }).foregroundColor(.primary)
                    Button(action: {
                        self.searchText = ""
                    }) {
                        Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
                    }
                }
                .padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
                .foregroundColor(.secondary)
                .background(Color(.secondarySystemBackground))
                .cornerRadius(10.0)
                if showCancelButton  {
                    Button("Cancel") {
                            UIApplication.shared.endEditing(true)
                            self.searchText = ""
                            self.showCancelButton = false
                    }
                    .foregroundColor(Color(.systemBlue))
                }
            }
            .padding(.horizontal)
            .navigationBarHidden(showCancelButton)
            List {
                ForEach(self.names.filter{
                    self.searchText.isEmpty ? $0.localizedStandardContains("") :
                        $0.localizedStandardContains(self.searchText)
                }, id: \.self) { name in
                    Text(name)
                }
            }
            .navigationBarTitle(Text("Vyhľadávanie"))
            .resignKeyboardOnDragGesture()
        }
    }
}

extension UIApplication {
    func endEditing(_ force: Bool) {
        self.windows
            .filter{$0.isKeyWindow}
            .first?
            .endEditing(force)
    }
}

struct ResignKeyboardOnDragGesture: ViewModifier {
    var gesture = DragGesture().onChanged{_ in
        UIApplication.shared.endEditing(true)
    }
    func body(content: Content) -> some View {
        content.gesture(gesture)
    }
}

extension View {
    func resignKeyboardOnDragGesture() -> some View {
        return modifier(ResignKeyboardOnDragGesture())
    }
}

struct SearchView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
           SearchView()
              .environment(\.colorScheme, .light)

           SearchView()
              .environment(\.colorScheme, .dark)
        }
    }
}

How can I make match the searchText with Cabinet.number or Person.name and list matching items with their path in JSON file e.g. "building.title > floor.title > cabinet.number" or for person "building.title > floor.title > cabinet.number > person.name"? Thank you for suggestions.

Upvotes: 1

Views: 877

Answers (1)

staticVoidMan
staticVoidMan

Reputation: 20274

Not entirely sure but I am assuming the search field can have either a person's name or a cabinet number based on which you want to filter your dataset and show a list in the fashion:

building.title > floor.title > cabinet.number

or

building.title > floor.title > cabinet.number > person.name

Maybe something along these lines might help:

struct ContentView: View {
  let data: UcmData
  @State var searchString = ""

  var found: [String] {
    var result = [String]()
    data.building.forEach { (building) in //go through every building
      building.floor.forEach { (floor) in //go through every floor
        floor.cabinet?.forEach { (cabinet) in //go through every cabinet
          var cabinetFound: String {
            return "\(building.title) > \(floor.title) > \(cabinet.number)"
          }

          if cabinet.number == searchString { //check with cabinet [!]
            result.append(cabinetFound) //add search result
          } else {
            cabinet.person.forEach { (person) in //go through every person
              if person.name == searchString { //check with person [!]
                let personFound = cabinetFound + " > \(person.name)"
                result.append(personFound) //add search result
              }
            }
          }
        }
      }
    }
    return result
  }

  var body: some View {
    VStack {
      TextField("search string", text: $searchString) //search field
      List(found, id: \.self) { (current) in //List of search results
        Text(current)
      }
    }
  }
}

*This example is just a sub-set of your view to showcase only the search functionality. Integrate it if it makes sense.

  1. found is an Array of String that will be computed when requested
  2. When search field updates, searchString updates and re-renders the body which will refresh the List with elements in found

I hope this helps :)

Upvotes: 1

Related Questions