skip to Main Content

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

    }
}

2

Answers


  1. 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
        }
    }
    
    Login or Signup to reply.
  2. 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) }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search