0

I need to filter the Binding in ForEach, by searching from SearchBar, but it's giving me an error. I am not sure if that is even possible in SwiftUI but if you know the way I could filter that please let me know. I would be really thankful for any help.

Here is how struct I am putting in ForEach look like:

struct Test: Identifiable {
    let id = UUID()
    var number: Int
    var text: String
}

Here is View with ForEach:

struct ContentView: View {
    @State var test = [
        Test(number: 1, text: "1"),
        Test(number: 2, text: "2"),
        Test(number: 3, text: "3"),
        Test(number: 4, text: "4"),
        Test(number: 5, text: "5"),
        Test(number: 6, text: "6")
    ]
    
    @State private var searchText = ""
    @State private var placeholder = "Search..."
    
    var body: some View {
        NavigationView{
            VStack{
                SearchBar(text: $searchText, placeholder: $placeholder)
                List {
                    ForEach($test.filter {
                        ($0.text.lowercased().contains(searchText.lowercased()) || searchText == ""}) { $data in
                        TestView(test: $data)
                    }
                }
            }
        }
    }
}

Here is my SearchBar:

struct SearchBar: UIViewRepresentable {
    
    @Binding var text: String
    @Binding var placeholder: String
    
    class Coordinator: NSObject, UISearchBarDelegate {
        
        @Binding var text: String
        @Binding var placeholder: String
        
        init(text: Binding<String>,  placeholder: Binding<String>) {
            _text = text
            _placeholder = placeholder
        }
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
        }
        
        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
            searchBar.setShowsCancelButton(true, animated: true)
        }
        
        func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
            searchBar.setShowsCancelButton(false, animated: true)
        }
        
        func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
            searchBar.resignFirstResponder()
        }
        
        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            searchBar.resignFirstResponder()
        }
    }
    
    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text, placeholder: $placeholder)
    }
    
    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let sb = UISearchBar(frame: .zero)
        sb.placeholder = context.coordinator.placeholder
        sb.delegate = context.coordinator
        return sb
    }
    
    func updateUIView(_ uiView: UISearchBar, context: Context) {
        uiView.text = text
    }
    
}

1 Answer 1

1

I think the issue is not the filtering per se, but the .lowercased() that the ForEach is rejecting. Rather than try to force that, there is a simpler way. Use a computed variable that does the filtering, and then roll your own Binding to send to the view like this:

struct ContentView: View {
    @State var testArray = [
        Test(number: 1, text: "1"),
        Test(number: 2, text: "2"),
        Test(number: 3, text: "3"),
        Test(number: 4, text: "4"),
        Test(number: 5, text: "5"),
        Test(number: 6, text: "6")
    ]
    
    @State private var searchText = ""
    @State private var placeholder = "Search..."
    
    var filteredTest: [Test] {
        // If you want to return no items when there is no matching filter use this:
        testArray.filter { ($0.text.lowercased().contains(searchText.lowercased()))}
        
        // If you want the whole array unless the filter matches use this:
        let returnTestArray = testArray.filter { ($0.text.lowercased().contains(searchText.lowercased()))}
        guard !returnTestArray.isEmpty else { return testArray }
        return returnTestArray
    }
    
    var body: some View {
        NavigationView{
            VStack{
                SearchBar(text: $searchText, placeholder: $placeholder)
                List {
                    ForEach(filteredTest) { test in
                        TestView10(test: Binding<Test> (
                            get: { test },
                            set: { newValue in
                                if let index = testArray.firstIndex(where: { $0.id == newValue.id } ) {
                                    testArray[index] = newValue
                                }
                            }
                        ))
                    }
                }
            }
        }
    }
}

I also renamed your array of data testArray to better reflect what it was, and in the ForEach, data now becomes test.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.