For a few days I keep digging into the SwiftUI framework and as long as it's really fun to play with I think... it's usable just in a playground for now. It's quite limited and for a more advanced application might be just easier to go with a UIKit. There is plenty of things which UIKit contains but have to be implemented in SwiftUI, and one of those things is a SearchView. In this week's article let's take a closer look at how to do it with a new fancy framework.
We can implement a search view in two ways. The first one is about wrapping the UIKit's SearchBar so it can be usable in SwiftUI, and the other one is to implement the view from scratch.
Thankfully developers responsible for the SwiftUI framework predicted that people will rather want to use UIKit components as well as many UIViews that are already implemented in their projects. So wrapping UISearchBar is as simple as conforming to the UIViewRepresentable
protocol where we have to implement three methods as you can see below.Coordinator
class will be responsible for communicating changes that occur in your UIKit view to other parts of your SwiftUI interface. For example, you can use a coordinator to delegate messages from your wrapped view to other SwiftUI views.
struct SearchBar: UIViewRepresentable {
// 1
@Binding var text: String
// 2
func makeUIView(context: Context) -> UISearchBar {
let searchBar = UISearchBar()
searchBar.delegate = context.coordinator
return searchBar
}
// 3
func updateUIView(_ uiView: UISearchBar, context: Context) {
uiView.text = text
}
// 4
func makeCoordinator() -> SearchBar.Coordinator {
Coordinator(text: $text)
}
// 5
class Coordinator: NSObject, UISearchBarDelegate {
@Binding var text: String
init(text: Binding<String>) {
_text = text
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
}
}
UISearchBar
instance and sets the delegate to the Coordinator
's objectUISearchBar
from the other SwiftUI's viewCoordinator
class that conforms to the previously set delegate
so we can react to text changes in UISearchBar
And that's all you need to wrap the UISearchBar
(or any other UIKit's view).
What if we would like to do it purely in SwiftUI though? It's not that hard in this case either and can do it in only a few lines of code, without having to add any external Assets as we can just use the system's images for it! Isn't that great? Just check the code below.
struct SearchBar: View {
@Binding var text: String
var body: some View {
HStack {
HStack {
Image(systemName: "magnifyingglass")
TextField("Search", text: $text)
.foregroundColor(.primary)
if !text.isEmpty {
Button(action: {
self.text = ""
}) {
Image(systemName: "xmark.circle.fill")
}
} else {
EmptyView()
}
}
.padding(EdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8)
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)
}
.padding(.horizontal)
}
}
There is still one thing missing that we rather need - closing the keyboard on list drag. It's not that simple in SwiftUI either, there is no easy way to resign the first responder. We need a little bit more of extra code - first of all an UIApplication
extension to force the end of editing of any currently focused text field.
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
And now we can attach a drag gesture to our list's view.
.gesture(DragGesture().onChanged { _ in
UIApplication.shared.endEditing(true)
})
It should resign the first responder correctly while dragging a list now.
Now we just have to create a view containing our search bar and a list.
struct SearchView: View {
let array = "SwiftUI is great but some views might need an extra work".components(separatedBy: " ")
@State private var searchText = ""
var body: some View {
VStack {
SearchBar(text: $searchText)
List {
ForEach(array.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self) {
searchText in Text(searchText)
}
}
.gesture(DragGesture().onChanged { _ in
UIApplication.shared.endEditing(true)
})
}
}
}
Here we use our previously created SearchBar
(either the pure SwiftUI or the one using UISearchBar
) and add a List of some items to search for as well as we attach a drag gesture to the List so we can hide keyboard on list drfagging.
We implemented SearchView using two techniques - a pure SwiftUI and wrapping UIKit's SearchBar component. I have no idea whether such views will be ever delivered in SwiftUI framework or will we always have to implement it ourselves but I really, really hope that they will single out this framework and release new versions independently as having to wait a year for major improvements is a really long time. They could even open-source it - why not? That would be even better I guess as we could implement any fixes or improvements on our own... sometimes there is just this little thing that is missing from the current implementation and we have re-do everything from scratch because there is no way to modify it in any way.
Thank you for reading!