Reputation: 9773
I have a view with the searchable modifier. It is always displaying a searchBar and works fine.
I find the searchBar is taking up too much space at the top of the view on an iPhone and since it is not used often, I want to only have a magnifying glass icon in the navigation bar. When the user presses that icon I want to show the search bar and hide it again after it becomes inactive.
Is there anyway to do this with the native searchBar (.searchable) without implementing a custom searchBar?
The view is not a list and I am not looking for the functionality of hiding the searchBar as the user scrolls. I just want to be able to control it's visibility with a button in the navigationBar.
struct SomeView: View {
@Environment(\.dismissSearch) private var dismissSearch
@State var searchString = ""
var body: some View {
NavigationStack {
MyView()
.searchable(text: $searchString, placement: .automatic)
.onSubmit(of: .search) {
// Hide SearchBar
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
// Show SearchBar
} label: {
Image(systemName: "magnifyingglass")
}
}
}
}
}
}
Upvotes: 4
Views: 3252
Reputation: 12992
At the moment there's no way to show/hide the search bar programmatically in SwiftUI. I think a solution can be found, but it strictly depends on your content view (I mean what you called MyView
). If your MyView
has a state that can be moved to a view model, you can easily do something like this:
struct SomeView: View {
@State private var searchString = ""
@State private var isSearchBarVisible = false
@StateObject private var contentViewModel = MyViewModel()
var body: some View {
NavigationStack {
if isSearchBarVisible {
content
.searchable(text: $searchString, placement: .automatic)
} else {
content
}
}
}
@ViewBuilder
private var content: some View {
MyView(viewModel: contentViewModel)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
isSearchBarVisible.toggle()
} label: {
Image(systemName: "magnifyingglass")
}
}
}
}
}
class MyViewModel: ObservableObject {
@Published private(set) var randomInt = 0
func getRandomInt() {
randomInt = Int.random(in: 0...999)
}
}
struct MyView: View {
@ObservedObject var viewModel: MyViewModel
var body: some View {
VStack {
Text("\(viewModel.randomInt)")
Button {
viewModel.getRandomInt()
} label: {
Text("Tap to get a random int")
}
}
}
}
The result is what you'd expect:
But, as I stated above, this solution works depending on your MyView
. In order to help you find a solution, it would be great to have more details about your MyView
.
EDIT: another, more complicated, example could be:
struct SomeView: View {
@State private var searchString = ""
@State private var isSearchBarVisible = false
var body: some View {
NavigationStack {
MyView(searchText: $searchString, isSearchBarVisible: $isSearchBarVisible)
}
}
}
struct MyView: View {
@Binding var searchText: String
@Binding var isSearchBarVisible: Bool
@State private var randomInt = 0
var body: some View {
ScrollView {
VStack {
Text("\(randomInt)")
Button {
randomInt = Int.random(in: 0...999)
} label: {
Text("Tap to get a random int")
}
if isSearchBarVisible {
titleView
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
} else {
titleView
}
ForEach(0...100, id: \.self) { n in
Text("\(n)")
}
}
}
}
@ViewBuilder private var titleView: some View {
Text("A list of numbers to take some vertical space:")
.padding()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
isSearchBarVisible.toggle()
} label: {
Image(systemName: "magnifyingglass")
}
}
}
}
}
The result is:
Depending on your needs, I think you can find a solution.
Upvotes: 4