Home > Software design >  How to replicate the search selection on iOS's Mail app?
How to replicate the search selection on iOS's Mail app?

Time:04-05

I have a list with a .searchable search bar on top of it. It filters a struct that has text, but also a date. I'm trying to have the user select whether he/she wants to search for all items containing the specific text, or search for all items with a data. I thought the way that iOS Mail did it when you hit he search bar on top is a good way (I'm open to other options tho...).

It looks like this:

enter image description here

So, when you tap the search field, the picker, or two buttons, or a tab selector shows up. I can't quite figure which is it. Regardless, I tried with a picker, but:

  1. I don't know where to place it
  2. I don't know how to keep it hidden until needed, and then hide it again.

this is the basic code:

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.dateText).font(.caption).fontWeight(.medium).foregroundColor(.secondary)
                    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] {
        data.collectables.filter {
        searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText)
        }
    }
}

And I was trying to add something like, taking into account isSearching:

@Environment(\.isSearching) var isSearching
var searchBy = [0, 1] // 0 = by text, 1 = by date
@State private var selectedSearch = 0

// Yes, I'd add the correct text to it, but I wanted to have it
// working first.
Picker("Search by", selection: $selectedColor) {
    ForEach(colors, id: \.self) {
          Text($0)
    }
}

How do I do it? How can I replicate that search UX from Mail? Or, is there any better way to let the user chose whether search text or date that appears when the user taps on the search?

CodePudding user response:

isSearching works on a sub view that has a searchable modifier attached. So, something like this would work:

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

    var body: some View {
        NavigationView {
            VStack {
                SearchableSubview(selectedItem: $selectedItem)
                List(filteredItems) { collectable in
                    VStack(alignment: .leading) {
                        Spacer()
                        Text(collectable.dateText).font(.caption).fontWeight(.medium).foregroundColor(.secondary)
                        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] {
        data.collectables.filter {
        searchText.isEmpty ? true : $0.text.localizedCaseInsensitiveContains(searchText)
        }
    }
}

struct SearchableSubview : View {
    @Environment(\.isSearching) private var isSearching
    @Binding var selectedItem : Int

    var body: some View {
        if isSearching {
            Picker("Search by", selection: $selectedItem) {
                Text("Choice 1").tag(0)
                Text("Choice 2").tag(1)
            }.pickerStyle(.segmented)
        }
    }
}
  • Related