How to use Search Scope in SwiftUI to improve search on iOS and macOS

Search functionality is a crucial feature of any app, and SwiftUI provides developers with powerful tools to create effective search experiences. One such tool is Search Scopes, which allows users to refine their search results by selecting specific categories or filters. By using Search Scopes in your SwiftUI app, you can provide a more targeted and personalized search experience, improving the user’s ability to find the content they need quickly and efficiently. In this blog post, we’ll explore how to use Search Scopes in SwiftUI to enhance search functionality on iOS and macOS. 

Search Scope is available for iOS 16+ and macOS 13+.

This blog is an addition to my previous post about SwiftUI Search Bar: Best Practices and Examples. Please have a look there if you need an overview of the SwiftUI search.

⬇️ Download the project files

Defining a Search Scope

As an example, I will continue using a Meal app that shows a list of meals. The Meal data is defined as follows:

struct Meal: Codable, Hashable, Identifiable {
    let imageURL: String
    let id: String
    let category: MealCategory
    let name: String
    let location: String
    let rating: Double
    var tags: [String]
}

The category is defined as follows:

enum MealCategory: String, Codable, CaseIterable, Identifiable, Hashable {
    case african = "African"
    case american = "American"
    case asian = "Asian"
    case europian = "Europian"
    case oceanian = "Oceanian"
    var id: String { rawValue }
}

When searching for a meal, my users can currently enter a search text in the search bar. I then use that to filter the meals that have this search term in the name:

struct MealListView: View {
    @StateObject var viewModel = MealListViewModel()
    @State var selectedMeal: Meal? = nil
    var body: some View {
        NavigationSplitView(sidebar: {
            List(selection: $selectedMeal) {
                ForEach(viewModel.filteredMeals) { meal in
                   MealCardView(meal: meal)
                        .tag(meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle("Find Your Next Meal")
        }, detail: {
            if let meal = selectedMeal {
                DetailView(meal: meal)
            } else {
                Text("Select a Meal")
            }
        })
}

How to add a Search Scope

In order to add a search scope, you can simply use the searchScopes modifier where you have to provide a binding to an Identifiable type used for selecting the scope. 

I am storing my state property for the selected scope in the view model, where I also will evaluate the filtered meals array:

class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = ""
    @Published var mealSearchScope: MealSearchScope = .all
    ...
}

In this case, I show all options for my scope category in a ForEach. The implementation is similar to a SwiftUI picker view. 

struct SearchScopeMealListView: View {
    @StateObject var viewModel = MealTokenListViewModel()
    @State var selectedMeal: Meal? = nil
    var body: some View {
        NavigationSplitView(sidebar: {
            List(selection: $selectedMeal) {
                ForEach(viewModel.meals) { meal in
                   MealCardView(meal: meal)
                        .tag(meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle("Find Your Next Meal")
        }, detail: {
            if let meal = selectedMeal {
                DetailView(meal: meal)
            } else {
                Text("Select a Meal")
            }
        })
        .searchable(text: $viewModel.searchText)
        .searchScopes($viewModel.mealSearchScope, scopes: {
            Text("All").tag(MealSearchScope.all)
            ForEach(MealCategory.allCases) { category in
                Text(category.rawValue)
                     .tag(MealSearchScope.category(category))
            }
        })
    }
}
swiftui search token example

On iOS, the search scope area is shown as a picker with a segmented picker style. When the user first enters the search text field, no search scope is displayed. Only once you start typing, will the scope be shown. 

example for how the interaction for search scope works on iOS with SwiftUI

 The initially selected scope is set to all because I set the initial value for the state property to all

class MealListViewModel: ObservableObject {
     ...
    @Published var mealSearchScope: MealSearchScope = .all
}

On macOS, the search scope is shown in the toolbar above the detail area. Once the user enters the search text field, the search scope area is shown.

Example for SwiftUI search scope on macOS

Showing Filtered Results

I am currently computing the filtered meal array in the view model with the search term. I can add now the information from the search scope:

class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = ""
    @Published var mealSearchScope: MealSearchScope = .all
    var filteredMeals: [Meal] {
        var meals = self.meals
        switch mealSearchScope {
            case .all: break
            case .category(let category):
                meals = meals.filter { $0.category == category }
        }
        if searchText.count > 0 {
            meals = meals.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
        }
        return meals
    }
    ...
}

Other Filter Options

If you want to support lower iOS and macOS versions, you can show filtering options with SwiftUI Picker. You can see different implementations in this post: “Master SwiftUI Picker View”. For example, you could add a picker or menu view in the toolbar. The picker options would be similar to the tokens.

Summary

Incorporating Search Scopes in your SwiftUI app can significantly improve the search functionality on iOS and macOS. Search Scopes allow users to filter and refine search results by selecting specific categories or filters. This provides users with a more targeted and personalized search experience, enabling them to quickly find the content they need. Additionally, Search Scopes can be especially useful for macOS apps, where users often work with large amounts of data and need to quickly and easily filter results. With Search Scopes, your SwiftUI app can offer a more efficient and intuitive search experience to your users.

Further Reading:

Leave a Comment

Subscribe to My Newsletter

Want the latest iOS development trends and insights delivered to your inbox? Subscribe to our newsletter now!

Newsletter Form

New Course Announcement!!!

50% OFF Launch Sale

MacOS Development with SwiftUI