A SwiftUI Search Bar — Part 2
Write a SwiftUI search bar just in plain SwiftUI without wrapping UIKit components like UISearchBar.
Previous Part: A SwiftUI search bar — Part 1
Now let’s style the search bar and add some feature to it.
- a common search bar like look
- an animated cancel-button
- hidden on start and shown on wipe down
The contact list of the Signal messenger app serves as a model.
Create a SearchBar View
First, let’s separate the Textfield
into its own View
called SearchBar
. This is a bit more cleaner and we can style the search field and the list independently.
Note: onChange
is now added to the new SearchBar
. The value is propagated thru the @Binding
.
Style the Search Bar
Let’s now style the search bar to a more common one. Therefore we put a magnifying glass Image
left next to the Textfield
by putting both into a HStack
. And then we lay the HStack
over a RoundedRectangle
.
Voila. It now looks like a common search bar. At the moment, the search bar is a little sticky to the borders, but we will solve it later.
Add a Cancel Button
Now we add a cancel button by wrapping it all up in another HStack
. Then we put a Button
right next to the just now styled TextField
.
Intermezzo: Change List to ScrollView
and LazyVStack
Before we continue to style and implementing the search bar behavior we should consider changing List
to ScrollView
and LazyVStack
. This comes with advantages and with disadvantages.
The Disadvantages. We lose some List features like the default swipe to delete action and we have to implement this feature by ourselves if it’s needed.
The Advantages. List
is limited to some Styles you can choose. The default style comes with these separator lines and you cannot easily disable them (on iOS14). And this style highlights the cell if you click on a button inside. A LazyVStack
does not have such limitations. You are free to style it for your needs.
Let’s replace the List
with a ScrollView
and a LazyVStack
. Therefore we place the SearchBar
and the Elements of our List
using ForEach
inside a LazyVStack
. Then we wrap the LazyVStack
with a ScrollView
.
Note: onAppear
is now added to the LazyVStack
!
Now the separator lines are gone and the search bar doesn’t stick to the borders anymore, because it’s now inside of the ScrollView
and the ScrollView
got padding.
Show and Hide the Cancel-Button
Like in the Signal messenger app, the search field is always visible on top of the list, but the cancel-button will only be shown if the user taps on the search field and will disappear if the user is done.
We add a new @State
property showCancelButton
to the SearchBar
that will store if the cancel-button is visible or not.
We then wrap the Button
in an if
condition to show the Button
if showCancelButton
is true
and hiding the Button
otherwise.
And we have to set showCancelButton
to true
if the user taps on the search field using onTapGesture
and turn it to false using the Buttons
action
parameter.
And of course, we have to empty the search field by setting text
to an empty String
. This will automatically call the FruitsController
search method which then updates the publishedFruits
to all fruits.
There is a little problem left over. After clicking on the cancel-button, the search field is still focused and the keyboard doesn’t disappear. Unfortunately, there is no built-in SwiftUI method to hide the keyboard and to clear the focus. But there is a solution. So let’s add the hideKeyboard method as an extension to the View class.
Now we can add the method to the action
of the cancel-button in the SearchBar
View.
As you see, now it works as it should.
Add Animation
To give the search bar a more sophisticated look and feel, let’s animate the appearance of the cancel-button a little. And this is (mainly) easy with SwiftUI. Every time we change the state of showCancelButton
in our code we have to wrap it up inside a withAnimation
block.
It’s not bad, but if you take a closer look, it could be better. At the moment, if the button fades out it intersects with the moving bar.
Add Transition
To solve this, let’s add a transition
to the Button
. A transition
to the right (trailing) edge.
It looks now as wanted.
Hide the Search Bar on Long Lists
And finally we want to hide the search bar on the appearance of the FruitsView
if the list is longer than the screen like in the Signal messenger app.
To do this we simply try to scroll the list to the first element of our publishedFruits
Array
which is the second element of our List. (The first is the SearchBar)
Therefore let’s wrap the LazyVStack
with a ScrollViewReader
. Then we can scroll to the first element of our Array
when the LazyVStack
appears using the proxy
of the ScrollViewReader
.
Important: Before this will take effect, we have to be sure, that the publishedFruits
Array
is long enough. So we need to add some more Fruits.
The result
Source Code: https://github.com/soeren-kirchner/SearchBarSwiftUI-Part2