SwiftUI Search: Getting Started

Learn how to use the searchable modifier to quickly add search capability to your SwiftUI apps. By Mina H. Gerges.

5 (4) · 3 Reviews

Download materials
Save for later
Share

Think about one of your favorite big apps whether it’s an e-commerce, social media, or booking app. How do you reach the content you want? Right, you go first to the search bar. Search is faster than scrolling through the long pages hoping to find your target.

In SwiftUI apps before the launch of SwiftUI 3, you had to create the whole search experience from scratch. At Apple’s Worldwide Developers Conference (WWDC) 2021, Apple introduced the searchable(text:placement:prompt:) modifier as a native solution to perform search queries in SwiftUI. It also comes with a variety of helper properties to improve the search experience.

In this tutorial, you’ll use this modifier to help Swifty search for his favorite meals’ recipes in an app that displays open-source recipes with their images.

Swifty cooking an egg in a frying pan

Along the way, you’ll learn:

  • How to use the searchable modifier.
  • Two ways to filter data using the search query.
  • How to give your users suggestions to improve their search experience.
  • Different positions where you can put your search field.
Note: This tutorial assumes you’re comfortable with developing SwiftUI apps using Xcode. If you’re not familiar with SwiftUI, check out SwiftUI: Getting Started first. You need Xcode 13 installed to follow this tutorial.

Getting Started

Click Download Materials at the top or bottom of this tutorial to download the starter project. Unzip it and open Chef Secrets.xcodeproj in the starter folder:

Chef Secrets Xcode File

This app displays a list of open-source recipes. When you select a recipe, you’ll see its image and ingredients.

Build and run on iPhone 12 Pro. Check the details of different recipes yourself:

List of recipes and recipe details

In Xcode, here are the files you’ll work on:

  • ContentView.swift: Contains NavigationView, which helps you navigate through the app views. It controls the split view in the iPad that displays recipes in the side view and the details of each recipe in the detail view.
  • RecipesView.swift: Displays the list of recipes in ChefRecipesModel.swift.
  • RecipeDetailView.swift: Displays the selected recipe’s details.
  • ChefRecipesModel.swift: Contains struct representing the recipes model. struct contains a function that decodes Recipes.json and loads it into recipes. It has two arrays you’ll use later to give your user search suggestions.
Note: If you don’t see a preview on the right of any file, that’s because you need to start/resume the preview feature. You can resume by clicking Resume next to Automatic preview updating paused or press Option-Command-P (⌥ + ⌘ + P).

Before you dig into the code, you should understand how the search process worked before Apple introduced searchable(text:placement:prompt:).

Understanding the History of Searching in SwiftUI

Before SwiftUI 3, UIKit didn’t have any view like UISearchBar. If you wanted to add a search experience to your app, you’d have to create the whole view from scratch.

Search Bar views numbered from 1 to 6

The search view would include the following views:

  1. Text: Where users type their search query.
  2. Image: Shows the magnifying glass.
  3. Button: Clears the search query.
  4. View: Contains the previously mentioned views.
  5. Button: Cancels the search process.
  6. HStack: Contains all the previous views.

Next, you’d handle all the events related to the search views, like:

  • Filtering the list according to the search query.
  • Showing and hiding the Clear button.
  • Clearing the search query when the user taps Clear.
  • Showing and hiding the Cancel button.

After covering the basics, you could start adding advanced features like search suggestions. You’d create an overlay to display the suggestions and handle what to show when in those suggestions.

With SwiftUI, you can create the whole previous search experience fast. But, SwiftUI 3 made it easier by offering a native solution for a search experience that does most of the previous tasks. You’ll learn about this in the next section.

Using the Searchable Modifier

SwiftUI now offers a native search experience using searchable(text:placement:prompt:). This modifier allows you to mark view content as being searchable, which means it can:

  • Display all the search views, including the search field and Cancel button.
  • Offer parameters/modifiers to handle the different events happening with those views.

In SwiftUI 3, you can add searchable(text:placement:prompt:) only to NavigationView. SwiftUI displays the search bar under the navigation bar title and above the list that you’ll filter. In multi-column view, you can choose in which view to display your search bar.

An iPhone screen shows a navigation bar with a search bar in its native position and list of animals below it

You’ve kept Swifty waiting for a little bit, but now you’ll help him search the Chef Secrets app for his next meal. :]

Searching for a Meal

Open RecipesView.swift. At the top of the view, add this line after the one which defines chefRecipesModel:

@State var searchQuery = ""

You’ll use this property to hold a search query entered by a user.

Next, add the searchable modifier to VStack، right before navigationBarTitleDisplayMode(_:):

.searchable(text: $searchQuery)

text is the text your user types in the search field. You bind it with searchQuery, which you’ll use later to filter the recipes according to the search query.

Note: Adding searchable(text:placement:prompt:) here gives the same effect as adding it to NavigationView inside ContentView.swift. You’ll use the second approach later in this tutorial.

Now, build and run. You’ll see that the recipes page doesn’t change. Where’s the search field, then? To answer this question, pull down the recipe list. Voila, you’ve found the missing search field.

A list of recipes with search field below the title

The default value for placement of the search field is .automatic. With the current view hierarchy, that means the search field is off-screen, and you have to pull down the list to display it. Later in this tutorial, you’ll learn more about other placement options.

You offer Swifty a place to type his search query, but you still haven’t performed the search query and displayed the filtered results. That’s what you’ll do next.

Performing a Search Query

Add the following property to RecipesView.swift:

@State var filteredRecipes = ChefRecipesModel().recipes

This variable will hold the list of recipes as filtered by the user’s search query.

Then, add the following method to RecipesView:

func filterRecipes() {
  if searchQuery.isEmpty {
    // 1
    filteredRecipes = chefRecipesModel.recipes
  } else {
    // 2
    filteredRecipes = chefRecipesModel.recipes.filter {
      $0.name
        .localizedCaseInsensitiveContains(searchQuery)
    }
  }
}

Here’s what this code does:

  1. If the search query is empty, filteredRecipes contains all the recipes.
  2. If the search query isn’t empty, you filter the recipes by name according to the search query. You ignore case sensitivity when filtering the recipes.

Now, you’ll use this new variable as data in the list. Inside ForEach in List, replace:

chefRecipesModel.recipes

With:

filteredRecipes

Next, add this code below searchable(text:placement:prompt:):

.onSubmit(of: .search) {
  filterRecipes()
}

.onSubmit(of:_:) tracks a chosen view, then triggers an action when a specific change happens to the value of this view. Here, you’re tracking the search field. The trigger fires when the user taps the Search button on the keyboard and then performs the action in the trailing block.

Note: When you put the cursor on the search field, SwiftUI displays Search as the text of the return key on the keyboard.

Finally, add this code as the second parameter inside searchable(text:placement:prompt:) after text:

prompt: "Search By Meal Name"

This sets the prompt inside the search bar, instructing the user on how to use it.

Build and run. Pull down the list to display the search field. You’ll see Search By Meal Name in the search bar’s placeholder:

The search field displays the Search By Meal Name prompt

Now, type Roast in the search field. Tap Search in the simulator keyboard. You’ll see the list filtered, displaying only the recipes with “Roast” in their names.

Recipes with titles filtered by the word Roast

Amazing — in just a few lines of code, you’ve filtered the recipe list!

Now, clear the search field by clicking × in the circle on the right side of the search box. Oh no, the list doesn’t change!

Filtered recipes without a search term

Finally, tap Cancel next to the search box. It disappears, but the list still doesn’t change.

Filtered recipes still show even when canceling a search

This isn’t the action you expected, is it? The recipe list should change when you clear the search field or tap Cancel. But that doesn’t happen because you only update the list when the user submits a search query. You’ll fix this in the next section.