How To Add Search to Your SwiftUI App

Chase
4 min readAug 26, 2023

--

If you have more than a handful of records in your app, search is one of the best user improvements you can add to help your users.

A screenshot of the search bar with content to search inside the search bar and the list displaying results that match the search

Initial Setup

To help us get straight to the topic of this article, we will start from the list we built in the following tutorial, don’t worry if you haven’t gone through that one yet, the method that we will go over in this tutorial will work for any list of data in SwiftUI: https://medium.com/@jpmtech/make-and-parse-an-api-call-using-swiftui-32f970e2b067

Add the textToSearch Variable

This is how we will know what to filter our list on. We will give it a default value of an empty string

// ContentView.swift
//..
@State private var textToSearch = ""

Add the Search Input Field

This small modifier that we add to our list gives us a search bar that shows up at the top of our list.

// ContentView.swift
//..
.searchable(text: $textToSearch, prompt: "Search")

Creating a Precise Filter

Adding the following computed property allows our results to be very precise. It will return an array that has exactly what the user searched for in the order that it was typed in the search field. We can make it slightly more inclusive by ignoring any case (upper case or lower case) in our search. While this may be right for one application, we will go over adding a more inclusive search in the next section.

// ContentView.swift
//..
var filteredData: [Post] {
if textToSearch.isEmpty {
return vm.postData
}

return vm.postData.filter { $0.title.localizedCaseInsensitiveContains(textToSearch) }
}

Creating a more inclusive search

Often we want to provide the users with results that aren’t limited to exactly what they type in. Instead we want to see if what they type in is in our content in any order. In order to create a more inclusive search, we can change our filteredData field to match any word in any order with the following code. We will go over how to display the results of our search in the next section.

// ContentView.swift
//..
var filteredData: [Post] {
if textToSearch.isEmpty {
return vm.postData
}

return vm.postData.filter { post in
textToSearch.split(separator: " ").allSatisfy {
string in post.title.lowercased().contains(string.lowercased())
}
}
}

Displaying our Results

Now that we have a list of posts that have been filtered by the search terms, we can display the results by updating our List view to use the filteredResults data instead of the vm.postData

A quick note for when you are trying to add search to your own app, the search bar will only display if you are in a NavigationStack or NavigationView, so we will wrap our existing list with a NavigationStack.

// ContentView.swift
//..
NavigationStack {
List(filteredResults) { post in
//..

Putting it all together

After adding all the changes to the code, our ContentView should now match the following code. Again if the code doesn’t look familiar, be sure to check out this tutorial first: https://medium.com/@jpmtech/make-and-parse-an-api-call-using-swiftui-32f970e2b067

//  ContentView.swift
import SwiftUI

struct ContentView: View {
@StateObject var vm = PostViewModel()
@State private var textToSearch = ""

// More precise search
// var filteredData: [Post] {
// if textToSearch.isEmpty {
// return vm.postData
// }
//
// return vm.postData.filter {
// $0.title.localizedCaseInsensitiveContains(textToSearch)
// }
// }

// More inclusive search
var filteredData: [Post] {
if textToSearch.isEmpty {
return vm.postData
}

return vm.postData.filter { post in
textToSearch.split(separator: " ").allSatisfy { string in
post.title.lowercased().contains(string.lowercased())
}
}
}

var body: some View {
NavigationStack {
List(filteredData) { post in
HStack {
Text("\(post.userId)")
.padding()
.overlay(Circle().stroke(.blue))

VStack(alignment: .leading) {
Text(post.title)
.bold()
.lineLimit(1)

Text(post.body)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(2)
}
}
}
.onAppear {
if vm.postData.isEmpty {
Task {
await vm.fetchData()
}
}
}
.searchable(text: $textToSearch, prompt: "Search")
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Running the code in our simulator, we should now see our search field and have the ability to search through the results in our list.

A screenshot of our search results being filtered by the content from our search field

If you got value from this article, please consider following me, clapping for this article, or sharing it to help others more easily find it.

If you have any questions on the topic, or know of another way to accomplish the same task, feel free to respond to the post or share it with a friend and get their opinion on it.

If you want to learn more about native mobile development, you can check out the other articles I have written here: https://medium.com/@jpmtech

If you want to see apps that have been built with native mobile development, you can check out my apps here: https://jpmtech.io/apps

Thank you for taking the time to check out my work!

--

--