Home > front end >  Case incensitive filter searchable text in an array of strings
Case incensitive filter searchable text in an array of strings

Time:04-11

I have an array of strings within a struct. This populates a list, and I use a .searchable to filter elements there. I can filter them by text, date, or tags within each item. All done without regard for the case.

Now, for the tags, I'm struggling to find the right way to filter them:

let dateFormatter = DateFormatter()

struct LibItem: Codable, Hashable, Identifiable {
    let id: UUID
    var text: String
    var date = Date()
    var dateText: String {
        dateFormatter.dateFormat = "EEEE, MMM d yyyy, h:mm a"
        return dateFormatter.string(from: date)
    }
    var tags: [String] = []
}

final class DataModel: ObservableObject {
    @AppStorage("myapp") public var collectables: [LibItem] = []

    init() {
        self.collectables = self.collectables.sorted(by: {
            $0.date.compare($1.date) == .orderedDescending
        })
    }

    func sortList() {
        self.collectables = self.collectables.sorted(by: {
            $0.date.compare($1.date) == .orderedDescending
        })
    }    
}

struct ContentView: View {
    @EnvironmentObject private var data: DataModel
    @State var searchText: String = ""

    var body: some View {
        NavigationView {
            List(filteredItems) { collectable in
                VStack(alignment: .leading) {
                    Spacer()
                    Text(collectable.text).font(.body).padding(.leading).padding(.bottom, 1)
                    Spacer()
                }
            }
            .listStyle(.plain)
            .searchable(
                text: $searchText,
                placement: .navigationBarDrawer(displayMode: .always),
                prompt: "Search..."
            )
        } 
    }

    var filteredItems: [LibItem] {       
        switch selectedItem {
        case 1: // date
            return data.collectables.filter {
                searchText.isEmpty ? true : $0.dateText.localizedCaseInsensitiveContains(searchText)
            }
        case 2: //tags <--- here how do I search and filter by, case insensitive?
            return data.collectables.filter {
                searchText.isEmpty ? true : $0.tags.contains(searchText)
            }
        default:
            return data.collectables.filter {
                searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText)
            }
        }

    }
}

CodePudding user response:

Compare two lowercased strings, by momentarily turning both sides of the comparison lowercased.

An example you can use in your code:

$0.tags.compactMap { $0.lowercased() }.contains(searchText.lowercased())

If you need to check whether each single item of tags contains part of the string, you need to iterate:

var isTagFound = false
$0.tags.compactMap { $0.lowercased() }.forEach {
    if $0.contains(searchText.lowercased()) {
        isTagFound = true
    }
}

CodePudding user response:

For strict equality, case insensitive:

return data.collectables.filter {
   searchText.isEmpty
      ? true
      : $0.tags.contains { tag in tag.caseInsensitiveCompare(searchText) == .orderedSame }
}

for substrings, you can use the range(of:) method:

return data.collectables.filter {
   searchText.isEmpty
      ? true
      : $0.tags.contains { tag in tag.range(of: searchText, options: .caseInsensitive) != nil }
}

or, use localizedCaseInsensitiveContains which you have already used before:

return data.collectables.filter {
   searchText.isEmpty
      ? true
      : $0.tags.contains { tag in tag.localizedCaseInsensitiveContains(searchText) }
}
  • Related