byrnec25
byrnec25

Reputation: 61

.searchable redrawing view

I'm getting tripped up an an issue with .searchable.

I want to use the environment object isSearching to hide a view, and in order to do that, according to apple docs, https://developer.apple.com/documentation/swiftui/performing-a-search-operation, I have to move the .searchable modifier outside of the view, and use an observable object or environment object to store the search text.

However on doing this, it seems to be redrawing the view after every letter is typed into the search bar, the excluded items appear for a split second and it looks janky.

I've tried with an environment object, injected from the root view, an observable object and a state object, same result.

I've attempted to include a minimum replicable example below. Is there a way to stop this from redrawing, or another way to access the isSearching variable?

Many thanks

/* This is the search view. This works fine, when I have the .searchable and the searchText variable in this view. */

struct Temp: View {
    @ObservedObject var searchModel: SearchModel
    @Environment(\.isSearching) private var isSearching
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: false)],
        animation: .default)
    private var savedItem: FetchedResults<Item>

    var body: some View {
        List {
            if isSearching {
            }
            else {
                withAnimation {
                    Button(action: {self.shouldPresentSheet.toggle()}) {
                        BeansListButton()
                            .contentShape(Rectangle())
                    }
                }.listRowBackground(Color("colorBeanListBG"))
            }
            ForEach(savedItem, id: \.self) { item in
                Section {
                    NavigationLink {
                        Detail(item: item)
                    } label: {
                        ZStack(alignment: .topTrailing){
                            ListItem(brand: item.brand!, product: item.product!, weight: item.weight, rating: Int(item.rating))
                        }
                    }
                }
                .listRowBackground(Color("colorBeanListBG"))
            }
            .onDelete(perform: deleteItems)
            .listRowSeparator(.hidden)
        }
        .onChange(of: searchModel.searchText) { search in
            let productKeyPredicate = NSPredicate(format: "product CONTAINS[cd] %@", search)
            let brandKeyPredicate = NSPredicate(format: "brand CONTAINS[cd] %@", search)
            let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [productKeyPredicate, brandKeyPredicate])
            
            if search.isEmpty {
                savedItem.nsPredicate = NSPredicate(value: true)
            } else {
                savedItem.nsPredicate = orPredicate
            }
        }
    }
}

/* But in order to access the isSearching environment object, everything needs to be nested, and an observable object is needed. 

This is the parent view */

@ObservedObject private var searchModel = SearchModel()

NavigationStack {
    Temp(searchModel: searchModel)
        .searchable(text: $searchModel.searchText, placement: .navigationBarDrawer(displayMode: .always))
}

/* And the ViewModel */ 
class SearchModel: ObservableObject {
    @Published var searchText: String = ""
}

Upvotes: 2

Views: 165

Answers (1)

byrnec25
byrnec25

Reputation: 61

I’ve found a workaround for this now, and I’m hiding the view using .onChange of searchText.

It’s not ideal as it only disappears now when you start typing not when the search bar has focus.

If anyone can tell me why it’s redrawing after every letter when passing the searchText as a binding please let me know thanks

Upvotes: 0

Related Questions