0

How can I optimize the searches in the list. I have two thousand records. I don't want to do a search through NSPredicate, because I want to pass what is in the field through a function that cleans up the numbers and reduces the letters, before comparing. Can you somehow give a delay so that it does not search immediately but after some time or if the user finishes typing. I also heard about something like Combine, but I have no idea how to use it.

Songbook List

import CoreData
import SwiftUI

struct SongbookView: View {
    @State var searchText: String = ""
    @Environment(\.managedObjectContext) var managedObjectContext
    @FetchRequest(
        entity: Song.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Song.number, ascending: true)]
    ) var songs: FetchedResults<Song>
    
    var body: some View {
        NavigationView{
            VStack{
                SearchBar(text: $searchText)
                Spacer()
                List(songs.filter({searchText.isEmpty ? true : removeNumber(str: $0.content!.lowercased()).contains(searchText.lowercased()) || String($0.number).contains(searchText)}), id:\.objectID) { song in
                    NavigationLink(destination: DetailView(song: song, isSelected: song.favorite)) {
                        HStack{
                            Text("\(String(song.number)). ") .font(.headline) + Text(song.title ?? "Brak tytułu")
                            if song.favorite {
                                Spacer()
                                Image(systemName: "heart.fill")
                                    .accessibility(label: Text("To jest ulubiona pieśń"))
                                    .foregroundColor(.red)
                            }
                        }.lineLimit(1)
                    }
                }.id(UUID())
                .listStyle(InsetListStyle())
                .animation(.default)
            }
            .padding(.top, 10)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    Text("Śpiewnik")
                        .font(.system(size: 20))
                        .bold()
                }
            }
        }
    }
    
    func removeNumber(str: String) -> String {
        var result = str
        let vowels: Set<Character> = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
        result.removeAll(where: { vowels.contains($0) })
        return result
    }
}

Search Bar

import SwiftUI

struct SearchBar: View {
    @Binding var text: String

    @State var isEditing = false
        
    var body: some View {
        HStack {
            
            TextField("Szukaj ...", text: $text)
                .padding(7)
                .padding(.horizontal, 25)
                .background(Color(.systemGray6))
                .cornerRadius(8)
                .overlay(
                    HStack {
                        Image(systemName: "magnifyingglass")
                            .foregroundColor(.gray)
                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                            .padding(.leading, 8)
                        
                        if isEditing {
                            Button(action: {
                                self.text = ""
                                
                            }) {
                                Image(systemName: "multiply.circle.fill")
                                    .foregroundColor(.gray)
                                    .padding(.trailing, 8)
                            }
                        }
                    }
                )
                .padding(.horizontal, 10)
                .onTapGesture {
                    self.isEditing = true
                }
            
            if isEditing {
                Button(action: {
                    self.isEditing = false
                    self.text = ""
                    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
                }) {
                    Text("Anuluj")
                }
                .padding(.trailing, 10)
                .transition(.move(edge: .trailing))
                .animation(.default)
            }
        }
    }
}

oculorum
  • 49
  • 5
  • If you don't want it in real time then don't put it in a SwiftUI `View`. Work (Anything except direct user interaction) should be kept somewhere else such as a ViewModel or a Manager of some kind. It sounds like you are trying to do something beyond the standard uses of SwiftUI wrappers maybe you should look at other approaches such as the `NSFetch...` options or the [dynamic predicates](https://developer.apple.com/wwdc21/10017) that can be updated in other ways. – lorem ipsum Sep 14 '21 at 18:08

1 Answers1

0

whenever you change the text in your SearchBar (that is every character you type), the SongbookView is updated because you are using a binding for text. What you want is to do the update only once when you press return. There are many ways to do this. A quick way to do this and keep your binding setup, is:

struct SearchBar: View {
    @Binding var text: String
    @State var txt: String = ""   // <--- here a temp var
    @State var isEditing = false
        
    var body: some View {
        HStack {
            TextField("Szukaj ...", text: $txt) // <--- here
                .onSubmit {
                    text = txt   // <--- here only update on return press
                }
                .padding(7)
                ....
               .onAppear {
                  txt = text   // <--- here if needed
               }
        

If you are using ios-14, use

        TextField("Szukaj ...", text: $txt, onCommit: {  // <--- here
            text = txt   // <--- here
        })