1

I got a re-usable searchbar in a separate view that looks like this:


struct SearchBar: View {
    
    @Binding var searchText: String
    @Binding var isSearching: Bool
    
    var body: some View {
        HStack {
            HStack {
                TextField("Search terms here", text: $searchText)
            }
            .onTapGesture(perform: {
                isSearching = true
            })
            .overlay(
                HStack {
                    Image(systemName: "magnifyingglass")
                    
                    if isSearching {
                        Button(action: { searchText = "" }, label: {
                            Image(systemName: "xmark.circle.fill")     
                        })
                    }   
                }
            )
            if isSearching {
                Button(action: {
                    isSearching = false
                    searchText = ""
                    
                }, label: {
                    Text("Cancel")
                        
                })
            }
            
        }
    }
}

And I'm using the SearchBar in multiple views, like this:

SearchBar(searchText: $textFieldSearch, isSearching: $isSearching)

Is there a way to override/append the functionality of the cancel button:

Button(action: {
   isSearching = false
   searchText = ""
   // pass more functionality here dynamically
 }, 
  label: {
  Text("Cancel")   
})

In some Views, I need to do some additional stuff besides clearing the searchText field and setting isSearching to false.

erikvm
  • 858
  • 10
  • 30

2 Answers2

1

You can use closure. Here I created one cancel button closure action and set it as optional.

struct SearchBar: View {
    
    @Binding var searchText: String
    @Binding var isSearching: Bool
    var cancel: (() -> Void)? // <== Here
    
    var body: some View {
        HStack {
            HStack {
                TextField("Search terms here", text: $searchText)
            }
            .onTapGesture(perform: {
                isSearching = true
            })
            .overlay(
                HStack {
                    Image(systemName: "magnifyingglass")
                    
                    if isSearching {
                        Button(action: { searchText = "" }, label: {
                            Image(systemName: "xmark.circle.fill")
                        })
                    }
                }
            )
            if isSearching {
                Button(action: {
                    isSearching = false
                    searchText = ""
                    cancel?() // <== Here
                }, label: {
                    Text("Cancel")
                    
                })
            }
            
        }
    }
}

Usage

SearchBar(searchText: $textFieldSearch, isSearching: $isSearching)
SearchBar(searchText: $textFieldSearch, isSearching: $isSearching) {
    // Cancel Action
}
Raja Kishan
  • 16,767
  • 2
  • 26
  • 52
  • ->`cancel?()`, How is that part used? Say you add the closure on `SearchBar` as demonstrated, what should the syntax look like inside the `Button(action:...{})`? – erikvm Oct 03 '21 at 11:48
  • First I declare cancel closure in view and call it from cancel button action. The closure is the one time of function but without a name. – Raja Kishan Oct 03 '21 at 11:50
  • I don't understand the `cancel?()` part though – erikvm Oct 03 '21 at 11:51
  • Can you please clear me to do you want to pass data from another view to the search view or search view to another view? – Raja Kishan Oct 03 '21 at 11:52
  • Okay, in one view (and one specific view only), when the user clicks "Cancel", it should make a call to a function called `clearResults()` - which simply makes an API call to my server. For all other views that use the "cancel" button, I want the regular functionality: setting `isSearching = false` and `searchText = ""` – erikvm Oct 03 '21 at 11:54
  • Just check the usage part and second option. You just need to call API function from the block. (Write your code after the cancel action comment.) – Raja Kishan Oct 03 '21 at 11:56
  • Getting `Type 'Void' cannot conform to 'View'; only struct/enum/class types can conform to protocols` – erikvm Oct 03 '21 at 11:57
  • I think you are doing something wrong. Could you please share your code? from my side compiled fine. – Raja Kishan Oct 03 '21 at 11:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237751/discussion-between-erikvm-and-raja-kishan). – erikvm Oct 03 '21 at 12:03
0

If you need addtional action then you can inject onCancel side effect like

struct SearchBar: View {
    
    @Binding var searchText: String
    @Binding var isSearching: Bool
    var onCancel: () -> Void = {}     // << default does nothing

    ...

            if isSearching {
                Button(action: {
                    isSearching = false
                    searchText = ""
                    onCancel()            // << here !!
                }, label: {
                    Text("Cancel")
                    
                })
            }

and use either in default way as you did or providing side effect, like

SearchBar(searchText: $textFieldSearch, isSearching: $isSearching) {
  // side effect is here
}
Asperi
  • 228,894
  • 20
  • 464
  • 690