<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>SwiftUI search &#8211; swiftyplace</title>
	<atom:link href="https://www.swiftyplace.com/blog/tag/swiftui-search/feed" rel="self" type="application/rss+xml" />
	<link>https://www.swiftyplace.com</link>
	<description>Learn how to build amazing apps with SwiftUI and Combine</description>
	<lastBuildDate>Fri, 09 Feb 2024 08:10:38 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://www.swiftyplace.com/wp-content/uploads/2023/08/cropped-logo-1-32x32.png</url>
	<title>SwiftUI search &#8211; swiftyplace</title>
	<link>https://www.swiftyplace.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>SwiftUI Search Bar: Best Practices and Examples</title>
		<link>https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=swiftui-search-bar-best-practices-and-examples</link>
					<comments>https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples#comments</comments>
		
		<dc:creator><![CDATA[Karin Prater]]></dc:creator>
		<pubDate>Mon, 01 May 2023 13:34:43 +0000</pubDate>
				<category><![CDATA[SwiftUI Components]]></category>
		<category><![CDATA[search bar Swift]]></category>
		<category><![CDATA[search bar SwiftUI]]></category>
		<category><![CDATA[SwiftUI search]]></category>
		<category><![CDATA[SwiftUI search bar]]></category>
		<category><![CDATA[SwiftUI searchable]]></category>
		<guid isPermaLink="false">https://swiftyplace.com/?p=1056</guid>

					<description><![CDATA[<p>Learn the best practices for implementing a SwiftUI search bar in your iOS app, with examples and tips to improve user experience.</p>
<p>The post <a rel="nofollow" href="https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples">SwiftUI Search Bar: Best Practices and Examples</a> appeared first on <a rel="nofollow" href="https://www.swiftyplace.com">swiftyplace</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">One of the essential elements of a great app is its ability to provide a seamless user experience. That&#8217;s why including a search bar is often crucial in making an app more user-friendly. SwiftUI search bar is a great tool to help users search for content within an app quickly. However, implementing it effectively requires understanding the best practices for designing and positioning the search bar and optimizing the search algorithms to provide accurate and relevant results.</p>



<p class="wp-block-paragraph">In this blog post, we&#8217;ll discuss the best practices for implementing the SwiftUI search bar and examples of apps that do it effectively. By following these best practices, you can make your app more user-friendly and help your users find the content they&#8217;re looking for quickly and easily. Let&#8217;s dive in</p>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



			
			
										
			
			


<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/gahntpo/SearchExampleApp" data-wplink-edit="true" target="_blank" rel="noopener">Download the project files</a></p>



<div style="height:47px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">What is a search bar in iOS?</h2>



<p class="wp-block-paragraph">A search bar is a user interface element that allows users to search for specific content within an app. In iOS apps, the search bar is often positioned at the top of the screen, making it easy for users to locate and use. Users can type keywords or phrases into the search bar, and the app will display results that match the search criteria. Search bars are particularly useful in apps that have a large amount of content, such as music, video, or news apps, where users may want to find specific content quickly and easily. The SwiftUI framework includes a searchable view modifier that developers can use to implement this functionality in their apps.</p>



<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-in-freeform-app-on-iOS.webp" alt="example for search bar on iOS"/><figcaption class="wp-element-caption">The new freeform app uses a search bar to search for drawing boards by title.</figcaption></figure>



<div style="height:43px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">How to add a SwiftUI search bar</h2>



<p class="wp-block-paragraph">Creating a search bar is quite easy. As an example, I will use a food delivery app. The following code defines a Meal model:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="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]
    
    static func preview() -&gt; Meal {
        Meal(imageURL: &quot;europian.jpg&quot;,
             id: &quot;3&quot;,
             category: MealCategory.oceanian,
             name: &quot;food name&quot;,
             location: &quot;Africa&quot;,
             rating: 4,
             tags: [&quot;Fast Food&quot;])
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Meal</span><span style="color: #F6F6F4">: Codable, Hashable, Identifiable {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> imageURL: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> id: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> category: MealCategory</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> name: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> location: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> rating: </span><span style="color: #97E1F1; font-style: italic">Double</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> tags: [</span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4">]</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">static</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">preview</span><span style="color: #F6F6F4">() </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> Meal {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">Meal</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">imageURL</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">europian.jpg</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">             </span><span style="color: #97E1F1">id</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">3</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">             </span><span style="color: #97E1F1">category</span><span style="color: #F6F6F4">: MealCategory.oceanian,</span></span>
<span class="line"><span style="color: #F6F6F4">             </span><span style="color: #97E1F1">name</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">food name</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">             </span><span style="color: #97E1F1">location</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Africa</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">             </span><span style="color: #97E1F1">rating</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">4</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">             </span><span style="color: #97E1F1">tags</span><span style="color: #F6F6F4">: [</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Fast Food</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">])</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Instead of fetching the data from a server, I will use mocked data that I load from the bundle. The following code defines the view model that sets up the data array for meals:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {

    @Published var meals = [Meal]()
    private let service = DataService()
    
    init() {
        meals = service.loadMealsFromDataStorage()
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">private</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> service </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">DataService</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">init</span><span style="color: #F6F6F4">() {</span></span>
<span class="line"><span style="color: #F6F6F4">        meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> service.</span><span style="color: #97E1F1">loadMealsFromDataStorage</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">I can then use the Meal data and show it in a SwiftUI List:</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="import SwiftUI

struct ContentView: View {

    @StateObject var viewModel = MealListViewModel()

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.meals) { meal in
                   MealCardView(meal: meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle(&quot;Find Your Next Meal&quot;)
        }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            List {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.meals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/meal-list-plain.jpg" alt="SwiftUI example project to test search"/></figure>
</div>
</div>



<div style="height:29px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Now we can add the search interface. First, you&#8217;ll need to create a state variable to hold the search query. I am going to add this to the view model.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = &quot;&quot;
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> searchText: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:19px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Then, you can add a search bar to your app&#8217;s view hierarchy using the searchable view modifier, which takes a binding to a String type:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="NavigationStack {
    List {
    ...
   }
   .searchable(text: $viewModel.searchText)
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">    List {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">   }</span></span>
<span class="line"><span style="color: #F6F6F4">   .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:19px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">The list needs to be embedded inside a Navigation View, NavigationStackView, or NavigationSplitView, in order for the search text field to appear in the navigation bar.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="import SwiftUI

struct ContentView: View {

    @StateObject var viewModel = MealListViewModel()

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.meals) { meal in
                   MealCardView(meal: meal)
                }
                .listRowSeparator(.hidden, 
                                  edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle(&quot;Find Your Next Meal&quot;)
            .searchable(text: $viewModel.searchText)
        }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            List {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.meals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span></span>
<span class="line"><span style="color: #F6F6F4">                                  </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/meal-list-with-search-bar-527x1024.jpg" alt="swiftui list with search field"/></figure>
</div>
</div>



<div style="height:42px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">How to Display Search Results</h2>



<p class="wp-block-paragraph">To implement the search functionality, you&#8217;ll need to filter data with the user&#8217;s search query.</p>



<p class="wp-block-paragraph">Add a computed property to compute the filtered array for meals. If the search text is empty, I don´t want to filter and instead return the full array of meals:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {

    @Published var meals = [Meal]()
    @Published var searchText: String = &quot;&quot;
    
    var filteredMeals: [Meal] {
        guard !searchText.isEmpty else { return meals }
        return meals.filter { meal in
            meal.name.lowercased().contains(searchText.lowercased())
        }
    }
     ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> searchText: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredMeals: [Meal] {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">guard</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">searchText.</span><span style="color: #BF9EEE">isEmpty</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> { </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> meals }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">            meal.name.</span><span style="color: #97E1F1">lowercased</span><span style="color: #F6F6F4">().</span><span style="color: #97E1F1">contains</span><span style="color: #F6F6F4">(searchText.</span><span style="color: #97E1F1">lowercased</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">     </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Now instead of the full list of meals, I am only showing the filtered data in the ForEach. The following example shows how the list displayed changes automatically when I enter a search text:.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="import SwiftUI

struct ContentView: View {

    @StateObject var viewModel = MealListViewModel()

    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.filteredMeals) { meal in
                   MealCardView(meal: meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle(&quot;Find Your Next Meal&quot;)
            .searchable(text: $viewModel.searchText)
        }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">SwiftUI</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            List {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredMeals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/meal-list-filtered-results-519x1024.jpg" alt="swiftui searchable in list"/></figure>
</div>
</div>



<div style="height:41px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Advanced Search Features in SwiftUI</h2>



<h3 class="wp-block-heading">Search Suggestions</h3>



<p class="wp-block-paragraph">SwiftUI offers an easy way to add search suggestions to your app by using the Searchable modifiers, which provide an optional parameter called &#8220;suggestions.&#8221; You can provide a view for this parameter, which might be a few static buttons or a dynamic set of suggestions generated from your app&#8217;s database or server.</p>



<p class="wp-block-paragraph">For this demo, I am using a constant property with default suggested search texts. I am not showing all suggestions, only the relevant ones that fit with the search text. Therefore I added another computed property which is ´filteredSuggestions´. The following code is a simple implementation to test search suggestions:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
    @Published var suggestions = [&quot;Muffin&quot;, &quot;Noodles&quot;, &quot;Beef&quot;, &quot;Wraps&quot;, &quot;Hamburger&quot;, &quot;Chicken&quot;,
                                  &quot;Falafel&quot;, &quot;Pita&quot;, &quot;Avocado&quot;, &quot;Tomato&quot;,
                                  &quot;Chocolate&quot;, &quot;Strawberry&quot;, &quot;Coffee&quot;]
    var filteredSuggestions: [String] {
        guard !searchText.isEmpty else { return [] }
        return suggestions.sorted().filter { $0.lowercased().contains(searchText.lowercased()) }
    }
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> suggestions </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Muffin</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Noodles</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Beef</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Wraps</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Hamburger</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Chicken</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">                                  </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Falafel</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Pita</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Avocado</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Tomato</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">                                  </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Chocolate</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Strawberry</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Coffee</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">]</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredSuggestions: [</span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4">] {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">guard</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">searchText.</span><span style="color: #BF9EEE">isEmpty</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> { </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> [] }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> suggestions.</span><span style="color: #97E1F1">sorted</span><span style="color: #F6F6F4">().</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">lowercased</span><span style="color: #F6F6F4">().</span><span style="color: #97E1F1">contains</span><span style="color: #F6F6F4">(searchText.</span><span style="color: #97E1F1">lowercased</span><span style="color: #F6F6F4">()) }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">I can then use these suggestions for the meal list view. When the user enters the search, I am not showing any suggestions. As soon as the search text is not empty, I use it to filter the relevant suggestions and show them below the search text field. When the user taps on a suggestion, the search text is updated to the selected suggestion:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText, suggestions: {
    ForEach(viewModel.filteredSuggestions, id: \.self) { suggestion in
        Button {
            viewModel.searchText = suggestion
        } label: {
           Label(suggestion, systemImage: &quot;bookmark&quot;)
        }
     }
})" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span><span style="color: #97E1F1">suggestions</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredSuggestions, </span><span style="color: #97E1F1">id</span><span style="color: #F6F6F4">: \.</span><span style="color: #F286C4">self</span><span style="color: #F6F6F4">) { suggestion </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">        Button {</span></span>
<span class="line"><span style="color: #F6F6F4">            viewModel.searchText </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> suggestion</span></span>
<span class="line"><span style="color: #F6F6F4">        } label</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">           </span><span style="color: #97E1F1">Label</span><span style="color: #F6F6F4">(suggestion, </span><span style="color: #97E1F1">systemImage</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">bookmark</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">     }</span></span>
<span class="line"><span style="color: #F6F6F4">})</span></span></code></pre></div>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Apple added a shorter form for this and replaces the button interaction with a ´searchCompletion´ modifier. The following code is equivalent to the previous implementation:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText, suggestions: {
    ForEach(viewModel.filteredSuggestions, id: \.self) { suggestion in
       Text(suggestion)
         .searchCompletion(suggestion)
     }
})" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span><span style="color: #97E1F1">suggestions</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredSuggestions, </span><span style="color: #97E1F1">id</span><span style="color: #F6F6F4">: \.</span><span style="color: #F286C4">self</span><span style="color: #F6F6F4">) { suggestion </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">       </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">         .</span><span style="color: #97E1F1">searchCompletion</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">     }</span></span>
<span class="line"><span style="color: #F6F6F4">})</span></span></code></pre></div>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Note that <strong>searchable(text:placement:prompt:suggestions:) </strong>has been soft deprecated with iOS 16.2 and macOS 13.1. You can use the newer searchSuggestions modifier instead</p>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="gb-container gb-container-c5dedc2e">
<div class="gb-container gb-container-f7a433c7">
<div class="gb-container gb-container-7a8dc7a3">

<figure class="gb-block-image gb-block-image-f04fbbad"><a href="https://www.swiftyplace.com/free-swiftui-layout-cookbook" target="_blank" rel="noopener noreferrer"><img fetchpriority="high" decoding="async" width="640" height="500" class="gb-image gb-image-f04fbbad" src="http://www.swiftyplace.com/wp-content/uploads/2024/01/swiftui_roadmap_preview.webp" alt="swiftui roadmap " title="swiftui_roadmap_preview" srcset="https://www.swiftyplace.com/wp-content/uploads/2024/01/swiftui_roadmap_preview.webp 640w, https://www.swiftyplace.com/wp-content/uploads/2024/01/swiftui_roadmap_preview-300x234.webp 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></figure>

</div>

<div class="gb-container gb-container-98352fe6">

<h2 class="gb-headline gb-headline-4aacd94c gb-headline-text">Feeling Lost in SwiftUI?</h2>



<p class="gb-headline gb-headline-5e6c4f85 gb-headline-text">This SwiftUI roadmap shows you what to learn next.</p>



<ul style="margin-top:0;margin-bottom:0;font-size:18px" class="wp-block-list">
<li>Key concepts at a glance</li>



<li>Spot your knowledge gaps</li>



<li>Guide your learning path</li>
</ul>


<div class="gb-container gb-container-34a8ad02">

<a class="gb-button gb-button-63e90e76 gb-button-blue" href="https://school.swiftyplace.com/f/swiftui-roadmap" target="_blank" rel="noopener noreferrer"><span class="gb-button-text">Get the FREE PDF</span><span class="gb-icon"><svg aria-hidden="true" role="img" height="1em" width="1em" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path></svg></span></a>

</div>


<p class="gb-headline gb-headline-7ef80e86 gb-headline-text">Ideal for beginners and self-learners.</p>

</div>
</div>
</div>


<div style="height:40px" aria-hidden="true" class="wp-block-spacer"></div>



<h4 class="wp-block-heading">Hidding Search Suggestions Depending on the Platform</h4>



<p class="wp-block-paragraph">If you want to hide search suggestions for some situations, you can use the <strong>searchSuggestions(_ visibility: Visibility, for placements:)</strong>&nbsp;modifier, which came with iOS 16 and macOS 13. For the placement, you have to specify SearchSuggestionsPlacement which has the cases of automatic, menu, and content. On macOS, search suggestions are displayed as a menu below the search text field, and on iOS, they are shown instead of the content.&nbsp;</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-suggestions-platform-1024x555.jpg" alt="Example for Search suggestions for macOS and iOS"/><figcaption class="wp-element-caption">For macOS, search suggestions appear as a menu below the search text field, and on iOS, they replace the content.</figcaption></figure>
</div>


<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">This means that the following will hide suggestions on iOS:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText, suggestions: {
    ForEach(viewModel.filteredSuggestions, id: \.self) { suggestion in
       Text(suggestion)
         .searchCompletion(suggestion)
     }
   .searchSuggestions(.hidden, for: .content)
})" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span><span style="color: #97E1F1">suggestions</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredSuggestions, </span><span style="color: #97E1F1">id</span><span style="color: #F6F6F4">: \.</span><span style="color: #F286C4">self</span><span style="color: #F6F6F4">) { suggestion </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">       </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">         .</span><span style="color: #97E1F1">searchCompletion</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">     }</span></span>
<span class="line"><span style="color: #F6F6F4">   .</span><span style="color: #97E1F1">searchSuggestions</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">for</span><span style="color: #F6F6F4">: .content)</span></span>
<span class="line"><span style="color: #F6F6F4">})</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">and if you want to hide suggestions on the Mac, you can set this:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchSuggestions(.hidden, for: .menu)" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchSuggestions</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">for</span><span style="color: #F6F6F4">: .menu)</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">If you for example want to show suggestions on iOS inside your main list. You can hide the suggestions on iOS only and use the environment for&nbsp;<strong>searchSuggestionsPlacement</strong> to check if you should show suggestions. I need to create another subview to properly get the environment value:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="struct CustomSearchSuggestionView: View {

    @ObservedObject var viewModel: MealListViewModel
    @Environment(\.searchSuggestionsPlacement) var placement

    var body: some View {
        if placement == .content {
            ForEach(viewModel.filteredSuggestions, id: \.self) { suggestion in
                Button {
                    viewModel.searchText = suggestion
                } label: {
                    Label(suggestion, systemImage: &quot;bookmark&quot;)
                }
            }
        }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">CustomSearchSuggestionView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@ObservedObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel: MealListViewModel</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Environment</span><span style="color: #F6F6F4">(\.searchSuggestionsPlacement) </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> placement</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> placement </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> .content {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredSuggestions, </span><span style="color: #97E1F1">id</span><span style="color: #F6F6F4">: \.</span><span style="color: #F286C4">self</span><span style="color: #F6F6F4">) { suggestion </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                Button {</span></span>
<span class="line"><span style="color: #F6F6F4">                    viewModel.searchText </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> suggestion</span></span>
<span class="line"><span style="color: #F6F6F4">                } label</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">Label</span><span style="color: #F6F6F4">(suggestion, </span><span style="color: #97E1F1">systemImage</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">bookmark</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="struct HideSuggestionMealListView: View {

    @StateObject var viewModel = MealListViewModel()
    @State var selectedMeal: Meal? = nil

    var body: some View {
        NavigationSplitView(sidebar: {
            List(selection: $selectedMeal) {
                CustomSearchSuggestionView(viewModel: viewModel)

                ForEach(viewModel.filteredMeals) { meal in
                    MealCardView(meal: meal)
                        .tag(meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle(&quot;Find Your Next Meal&quot;)
            .searchable(text: $viewModel.searchText, suggestions: {
                ForEach(viewModel.filteredSuggestions, id: \.self) { suggestion in
                    Text(suggestion)
                        .searchCompletion(suggestion)
                }
               .searchSuggestions(.hidden, for: .content)
            })
        }, detail: {
            if let meal = selectedMeal {
                DetailView(meal: meal)
            } else {
                Text(&quot;Select a Meal&quot;)
            }
        })
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">HideSuggestionMealListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@State</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">nil</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">NavigationSplitView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">sidebar</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">List</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selection</span><span style="color: #F6F6F4">: $selectedMeal) {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">CustomSearchSuggestionView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">viewModel</span><span style="color: #F6F6F4">: viewModel)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredMeals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                        .</span><span style="color: #97E1F1">tag</span><span style="color: #F6F6F4">(meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span><span style="color: #97E1F1">suggestions</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredSuggestions, </span><span style="color: #97E1F1">id</span><span style="color: #F6F6F4">: \.</span><span style="color: #F286C4">self</span><span style="color: #F6F6F4">) { suggestion </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">                        .</span><span style="color: #97E1F1">searchCompletion</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">               .</span><span style="color: #97E1F1">searchSuggestions</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">for</span><span style="color: #F6F6F4">: .content)</span></span>
<span class="line"><span style="color: #F6F6F4">            })</span></span>
<span class="line"><span style="color: #F6F6F4">        }, </span><span style="color: #97E1F1">detail</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> meal </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> selectedMeal {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">DetailView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">            } </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Select a Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-suggestion-hidden-custom-531x1024.jpg" alt="SwiftUI example for search with custom suggestion placement"/></figure>
</div>
</div>



<p class="wp-block-paragraph">I modified the placement of search suggestions only. For macOS, they are still displayed in the default position, which is in a menu below the search text field.</p>



<div style="height:56px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Search Tokens</h3>



<p class="wp-block-paragraph">A newer feature for iOS 16 and macOS 13 are search tokens. Let&#8217;s say I want to allow the user of my app to search for food that is on the go or on sale. My data has a tag property that I can use to filter, but how do I allow my users to pick these options? You might want to use a picker, but tokens are a much smoother user experience. Learn how to use search tokens in this blog post&nbsp;<a href="https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos" target="_blank" rel="noopener">Search Tokens in SwiftUI</a></p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-tokens.jpg" alt=""/></figure>
</div>


<div style="height:53px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Creating a Scope Bar to Filter Results</h3>



<p class="wp-block-paragraph">You can add a scope bar to your search bar for filtering by Category. This scope control limits searches by simply searching for specific scope areas.&nbsp;Using scope bars can be accomplished easily by simply implementing a new <strong>searchScope</strong> modifier.</p>



<p class="wp-block-paragraph">As an example, I could use the origin of the meal to limit the search scope. If you want to see the full breakdown of how I implemented search scope, check out this post about&nbsp;<a href="https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos" target="_blank" rel="noopener">How to use Search Scopes in SwiftUI to improve search on iOS and macOS</a>.</p>



<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-scope-macos-1024x704.jpg" alt=""/></figure>



<div style="height:41px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Other Filter Options</h2>



<p class="wp-block-paragraph">If you want to support filtering options, you can show a&nbsp;SwiftUI Picker. You can see different implementations in this post:<a href="https://www.swiftyplace.com/blog/swiftui-picker-made-easy-tutorial-with-example" target="_blank" rel="noopener"> &#8220;Master SwiftUI Picker View&#8221;</a>. For example, you could add a picker or menu view in the toolbar. The picker options would be similar to the tokens.</p>



<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Detecting and Dismissing a Search Field Programmatically</h2>



<p class="wp-block-paragraph">In some cases, you may want to programmatically detect when a user is interacting with the search field, or dismiss the search field when certain conditions are met. In SwiftUI, you can use the Environment value for&nbsp;<strong><br></strong><strong>isSearching </strong>and<strong> dismissSearch</strong>. You have to access this value from a view that is inside the search list.&nbsp;</p>



<p class="wp-block-paragraph">For example, if you want to show a different list for the search results. I am extracting my List in a reusable view like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="struct MealListContentView: View {

    @Binding var selectedMeal: Meal?
    let meals: [Meal]

    var body: some View {
        List(selection: $selectedMeal) {
            ForEach(meals) { meal in
                MealCardView(meal: meal)
                    .tag(meal)
            }
            .listRowSeparator(.hidden, edges: .all)
            .listRowInsets(.init(top: 10, leading: 10,
                                 bottom: 10, trailing: 10))
        }
        .listStyle(.plain)
        .navigationTitle(&quot;Find Your Next Meal&quot;)

    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealListContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Binding</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> meals: [Meal]</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">List</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selection</span><span style="color: #F6F6F4">: $selectedMeal) {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(meals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">tag</span><span style="color: #F6F6F4">(meal)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listRowInsets</span><span style="color: #F6F6F4">(.</span><span style="color: #F286C4">init</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">top</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">leading</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">                                 </span><span style="color: #97E1F1">bottom</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">trailing</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE">10</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">I create a separate view to decide what view to show. If the user is in search mode, I will show an overlay with the search results. The check is done with the isSearching property and if the user entered a valid search term:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="private struct MealSearchListView: View {

    @ObservedObject var viewModel: MealListViewModel
    @Environment(\.isSearching) var isSearching
    @Binding var selectedMeal: Meal?

    var body: some View {
        MealListContentView(selectedMeal: $selectedMeal,
                            meals: viewModel.meals)
            .overlay {
                if isSearching &amp;&amp; !viewModel.searchText.isEmpty {
                    VStack {
                        Text(&quot;Search Results&quot;)
                        MealListContentView(selectedMeal: $selectedMeal,
                                            meals: viewModel.filteredMeals)
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color(&quot;background&quot;))
                }
            }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">private</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealSearchListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@ObservedObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel: MealListViewModel</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Environment</span><span style="color: #F6F6F4">(\.isSearching) </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> isSearching</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Binding</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">MealListContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal,</span></span>
<span class="line"><span style="color: #F6F6F4">                            </span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.meals)</span></span>
<span class="line"><span style="color: #F6F6F4">            .overlay {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> isSearching </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">viewModel.searchText.</span><span style="color: #BF9EEE">isEmpty</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                    VStack {</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Search Results</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">MealListContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal,</span></span>
<span class="line"><span style="color: #F6F6F4">                                            </span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.filteredMeals)</span></span>
<span class="line"><span style="color: #F6F6F4">                    }</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">frame</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">maxWidth</span><span style="color: #F6F6F4">: .</span><span style="color: #BF9EEE">infinity</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">maxHeight</span><span style="color: #F6F6F4">: .</span><span style="color: #BF9EEE">infinity</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">background</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">Color</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">background</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="struct IsSearchingMealListView: View {
    @StateObject var viewModel = MealListViewModel()
    @State var selectedMeal: Meal? = nil
    var body: some View {
        NavigationStack {
            MealSearchListView(viewModel: viewModel,
                                 selectedMeal: $selectedMeal)
                .searchable(text: $viewModel.searchText)
        }
    }
}
private struct MealSearchListView: View {
    @ObservedObject var viewModel: MealListViewModel
    @Environment(\.isSearching) var isSearching
    @Binding var selectedMeal: Meal?
    var body: some View {
        MealListContentView(selectedMeal: $selectedMeal,
                            meals: viewModel.meals)
            .overlay {
                if isSearching &amp;&amp; !viewModel.searchText.isEmpty {
                    VStack {
                        Text(&quot;Search Results&quot;)
                        MealListContentView(selectedMeal: $selectedMeal,
                                            meals: viewModel.filteredMeals)
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color(&quot;background&quot;))
                }
            }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">IsSearchingMealListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@State</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">nil</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">MealSearchListView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">viewModel</span><span style="color: #F6F6F4">: viewModel,</span></span>
<span class="line"><span style="color: #F6F6F4">                                 </span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal)</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span>
<span class="line"><span style="color: #F286C4">private</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealSearchListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@ObservedObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel: MealListViewModel</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Environment</span><span style="color: #F6F6F4">(\.isSearching) </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> isSearching</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Binding</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">MealListContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal,</span></span>
<span class="line"><span style="color: #F6F6F4">                            </span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.meals)</span></span>
<span class="line"><span style="color: #F6F6F4">            .overlay {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> isSearching </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">viewModel.searchText.</span><span style="color: #BF9EEE">isEmpty</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                    VStack {</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Search Results</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">MealListContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal,</span></span>
<span class="line"><span style="color: #F6F6F4">                                            </span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.filteredMeals)</span></span>
<span class="line"><span style="color: #F6F6F4">                    }</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">frame</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">maxWidth</span><span style="color: #F6F6F4">: .</span><span style="color: #BF9EEE">infinity</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">maxHeight</span><span style="color: #F6F6F4">: .</span><span style="color: #BF9EEE">infinity</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">background</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">Color</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">background</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-isSearching-example-1-520x1024.jpg" alt="swiftui is searching"/></figure>
</div>
</div>



<div style="height:23px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">In addition, you can programmatically dismiss the search. I can add a done button in the search results and use the dismissSeach Environment property:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * 1rem);--cbp-line-highlight-color:rgba(251, 251, 239, 0.2);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="private struct MealSearchListView: View {

    @Environment(\.isSearching) var isSearching
    @Environment(\.dismissSearch) var dismissSearch

    @ObservedObject var viewModel: MealListViewModel
    @Binding var selectedMeal: Meal?

    var body: some View {
        MealListContentView(selectedMeal: $selectedMeal,
                            meals: viewModel.meals)
            .overlay {
                if isSearching &amp;&amp; !viewModel.searchText.isEmpty {
                    VStack {
                        HStack {
                            Text(&quot;Search Results&quot;)

                            Button(&quot;Done&quot;) {
                                dismissSearch()
                            }
                        }
                        MealListContentView(selectedMeal: $selectedMeal,
                                            meals: viewModel.filteredMeals)
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .background(Color(&quot;background&quot;))
                }
            }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">private</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealSearchListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"></span>
<span class="line cbp-line-highlight"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Environment</span><span style="color: #F6F6F4">(\.isSearching) </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> isSearching</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Environment</span><span style="color: #F6F6F4">(\.dismissSearch) </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> dismissSearch</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@ObservedObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel: MealListViewModel</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Binding</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">MealListContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal,</span></span>
<span class="line"><span style="color: #F6F6F4">                            </span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.meals)</span></span>
<span class="line"><span style="color: #F6F6F4">            .overlay {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> isSearching </span><span style="color: #F286C4">&amp;&amp;</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">!</span><span style="color: #F6F6F4">viewModel.searchText.</span><span style="color: #BF9EEE">isEmpty</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                    VStack {</span></span>
<span class="line"><span style="color: #F6F6F4">                        HStack {</span></span>
<span class="line"><span style="color: #F6F6F4">                            </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Search Results</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">                            </span><span style="color: #97E1F1">Button</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Done</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">) {</span></span>
<span class="line cbp-line-highlight"><span style="color: #F6F6F4">                                </span><span style="color: #97E1F1">dismissSearch</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">                            }</span></span>
<span class="line"><span style="color: #F6F6F4">                        }</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">MealListContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $selectedMeal,</span></span>
<span class="line"><span style="color: #F6F6F4">                                            </span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.filteredMeals)</span></span>
<span class="line"><span style="color: #F6F6F4">                    }</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">frame</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">maxWidth</span><span style="color: #F6F6F4">: .</span><span style="color: #BF9EEE">infinity</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">maxHeight</span><span style="color: #F6F6F4">: .</span><span style="color: #BF9EEE">infinity</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">background</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">Color</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">background</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">))</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:31px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="gb-container gb-container-c5dedc2e">
<div class="gb-container gb-container-f7a433c7">
<div class="gb-container gb-container-7a8dc7a3">

<figure class="gb-block-image gb-block-image-f04fbbad"><a href="https://www.swiftyplace.com/free-swiftui-layout-cookbook" target="_blank" rel="noopener noreferrer"><img fetchpriority="high" decoding="async" width="640" height="500" class="gb-image gb-image-f04fbbad" src="http://www.swiftyplace.com/wp-content/uploads/2024/01/swiftui_roadmap_preview.webp" alt="swiftui roadmap " title="swiftui_roadmap_preview" srcset="https://www.swiftyplace.com/wp-content/uploads/2024/01/swiftui_roadmap_preview.webp 640w, https://www.swiftyplace.com/wp-content/uploads/2024/01/swiftui_roadmap_preview-300x234.webp 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></figure>

</div>

<div class="gb-container gb-container-98352fe6">

<h2 class="gb-headline gb-headline-4aacd94c gb-headline-text">Feeling Lost in SwiftUI?</h2>



<p class="gb-headline gb-headline-5e6c4f85 gb-headline-text">This SwiftUI roadmap shows you what to learn next.</p>



<ul style="margin-top:0;margin-bottom:0;font-size:18px" class="wp-block-list">
<li>Key concepts at a glance</li>



<li>Spot your knowledge gaps</li>



<li>Guide your learning path</li>
</ul>


<div class="gb-container gb-container-34a8ad02">

<a class="gb-button gb-button-63e90e76 gb-button-blue" href="https://school.swiftyplace.com/f/swiftui-roadmap" target="_blank" rel="noopener noreferrer"><span class="gb-button-text">Get the FREE PDF</span><span class="gb-icon"><svg aria-hidden="true" role="img" height="1em" width="1em" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"></path></svg></span></a>

</div>


<p class="gb-headline gb-headline-7ef80e86 gb-headline-text">Ideal for beginners and self-learners.</p>

</div>
</div>
</div>


<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Performing a task when the user finishes the search</h3>



<p class="wp-block-paragraph">If your data comes from a server, you need to do a lot of fetching if you react to every user input. The way Apple recommends this is by using the onSubmit modifier. You can specify text or search for onSubmit. If I only wanted to execute my fetching when the user presses enter in the search text field, the following implementation would work:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="NavigationStack {
    List {
        ...
    }
    .searchable(text: $viewModel.searchText)

    .onSubmit(of: .search) {
        print(&quot;fetch data from server to refresh with full search query&quot;)
        // viewModel.performQuery()
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">    List {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #F6F6F4">    .</span><span style="color: #97E1F1">onSubmit</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">of</span><span style="color: #F6F6F4">: .search) {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">print</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">fetch data from server to refresh with full search query</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #7B7F8B">// viewModel.performQuery()</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:33px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Changing the Appearance of the Search</h2>



<p class="wp-block-paragraph">Per default, the text field shows the phrase &#8220;search&#8221;. This is also automatically localized in different languages. If you want to change the placeholder, you can use the prompt argument of the searchable modifier:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText, prompt: &quot;Search for Meals&quot;)" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span><span style="color: #97E1F1">prompt</span><span style="color: #F6F6F4">: </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Search for Meals</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span></code></pre></div>



<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">How to always show the search bar</h3>



<p class="wp-block-paragraph">For iOS, the search text field is not shown directly in certain situations. Only after the user pulls down the list, will it appear.&nbsp;</p>



<p class="wp-block-paragraph">If the search is inside the first view of a NavigationStack or the sidebar of a NavigationSplitView, the search text field is shown directly. If the seach is in the second or later view, the search is hidden initially.</p>



<p class="wp-block-paragraph">You can override this behavior by using the search placement like so:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText, 
            placement: .navigationBarDrawer(displayMode:.always))
" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">placement</span><span style="color: #F6F6F4">: .</span><span style="color: #97E1F1">navigationBarDrawer</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">displayMode</span><span style="color: #F6F6F4">:.always))</span></span>
<span class="line"></span></code></pre></div>



<div style="height:41px" aria-hidden="true" class="wp-block-spacer"></div>



<h3 class="wp-block-heading">Search Placement</h3>



<p class="wp-block-paragraph">If you have an app with multiple screens or a NavigationSplitView, things get a bit more interesting because you can add the search in different parts of your app. As an example, I am showing a 3-column NavigationSplitView, where the sidebar has a list of all the meal categories.</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="NavigationSplitView(columnVisibility: $columnVisibility) {
    SidebarView(selectedCategory: $viewModel.selectedCategory)
} content: {
    ContentView(meals: viewModel.filteredMeals,
                selectedMeal: $viewModel.selectedMeal)
} detail: {
    if let meal = viewModel.selectedMeal {
        DetailView(meal: meal)
    } else {
        Text(&quot;Detail&quot;)
    }
}
.searchable(text: $viewModel.searchText)" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #97E1F1">NavigationSplitView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">columnVisibility</span><span style="color: #F6F6F4">: $columnVisibility) {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">SidebarView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selectedCategory</span><span style="color: #F6F6F4">: $viewModel.selectedCategory)</span></span>
<span class="line"><span style="color: #F6F6F4">} content</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">ContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.filteredMeals,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $viewModel.selectedMeal)</span></span>
<span class="line"><span style="color: #F6F6F4">} detail</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> meal </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> viewModel.selectedMeal {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">DetailView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">    } </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Detail</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span>
<span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">If I add a searchable modifier to the&nbsp;NavigationSplitView, the default placement on macOS&nbsp;will be used and the search text field appears in the toolbar on&nbsp;the&nbsp;trailing&nbsp;corner.</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-macos-placement-default-1024x550.jpg" alt="SwiftUI search bar placement on macOS with the toolbar placement"/><figcaption class="wp-element-caption">Per default, the search bar is added to the toolbar on macOS</figcaption></figure>
</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-macos-placement-sidebar-1024x549.jpg" alt="SwiftUI search bar placement on macOS with the sidebar placement"/><figcaption class="wp-element-caption">You can modify the search placement on macOS by specifying the sidebar placement.</figcaption></figure>
</div>


<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">In order to change the search bar placement you can either specify a different placement like in the following:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText, placement: .sidebar)" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText, </span><span style="color: #97E1F1">placement</span><span style="color: #F6F6F4">: .sidebar)</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">The same app on the iPhone would then add the search bar to the sidebar view.</p>



<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-iphone-sidebar-placement@1.5x.jpg" alt="Example for SwiftUI search in NavigationSplitView. Per default in the iPhone, the search bar is added to the sidebar area"/><figcaption class="wp-element-caption">Per default in the iPhone, the search bar is added to the sidebar area</figcaption></figure>



<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">You can change the placement of the search bar by moving the searchable view modifier to the content area:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="NavigationSplitView(columnVisibility: $columnVisibility) {
    ...
} content: {
    ContentView(meals: viewModel.filteredMeals,
                selectedMeal: $viewModel.selectedMeal)
    .searchable(text: $viewModel.searchText,
                placement: .navigationBarDrawer(displayMode: .always))
} detail: {
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #97E1F1">NavigationSplitView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">columnVisibility</span><span style="color: #F6F6F4">: $columnVisibility) {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">} content</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #97E1F1">ContentView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meals</span><span style="color: #F6F6F4">: viewModel.filteredMeals,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">selectedMeal</span><span style="color: #F6F6F4">: $viewModel.selectedMeal)</span></span>
<span class="line"><span style="color: #F6F6F4">    .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">placement</span><span style="color: #F6F6F4">: .</span><span style="color: #97E1F1">navigationBarDrawer</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">displayMode</span><span style="color: #F6F6F4">: .always))</span></span>
<span class="line"><span style="color: #F6F6F4">} detail</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">I am using the display mode to always because the search UI would otherwise only be visible after the user pulls the list.</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-iphone-content-placement@1.5x-1024x659.jpg" alt="SwiftUI search field placement to the content area"/><figcaption class="wp-element-caption">You can modify the search placement on iOS to the content area.</figcaption></figure>
</div>


<div style="height:21px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Similarly, we can change the placement on the iPad&nbsp;for a 3-column layout.</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-ipad-content-placement-1024x751.jpg" alt="Example for search bar placement on the iPad in SwiftUI"/><figcaption class="wp-element-caption">Per default on the iPad, the search field is shown in the content area.</figcaption></figure>
</div>


<div style="height:23px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">If you want to display the search bar in the sidebar area, you have to move the searchable view modifier inside the NavigationSplitView and attach it to the sidebar view:</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:1rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="NavigationSplitView(columnVisibility: $columnVisibility) {
   SidebarView()
        .searchable(text: $viewModel.searchText)
} content: {
    ...
} detail: {
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #97E1F1">NavigationSplitView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">columnVisibility</span><span style="color: #F6F6F4">: $columnVisibility) {</span></span>
<span class="line"><span style="color: #F6F6F4">   </span><span style="color: #97E1F1">SidebarView</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"><span style="color: #F6F6F4">} content</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">} detail</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:20px" aria-hidden="true" class="wp-block-spacer"></div>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-ipad-sidebar-placement-1024x751.jpg" alt="Example for search bar placement on the iPad in SwiftUI"/><figcaption class="wp-element-caption">Moving the searchable view modifier, you can modify the search bar placement to the sidebar.</figcaption></figure>
</div>


<div style="height:39px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">Through the &#8220;SwiftUI Search Bar: Best Practices and Examples&#8221; blog post, you have learned how to add and customize a search bar in SwiftUI, including its placement, search result display, search suggestions, and programmatically dismissing the search. Additionally, you saw&nbsp;how to perform tasks upon completion of the search. By following the best practices outlined in the post and implementing the examples provided, you can create a more efficient and user-friendly search experience in my SwiftUI app.</p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/gahntpo/SearchExampleApp" target="_blank" rel="noopener">Download the project files</a></p>



<p class="wp-block-paragraph"><strong>Further Reading:</strong></p>



<ul class="wp-block-list">
<li>Use Search with Core Data: <a href="https://school.swiftyplace.com/courses/swiftui-and-core-data" target="_blank" rel="noopener">SwiftUI and Core Data Course</a></li>



<li><a href="https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos" target="_blank" rel="noopener">Search Tokens for advanced search queries</a></li>



<li><a href="https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos" target="_blank" rel="noopener">Search Scope for advanced search queries</a> &#8211; a must for macOS apps</li>



<li><a href="https://www.swiftyplace.com/blog/swiftui-picker-made-easy-tutorial-with-example" target="_blank" rel="noopener">Master SwiftUI Picker</a></li>



<li><a href="https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/search-fields/" target="_blank" rel="noopener">Human interface guidelines for search</a></li>
</ul>
<p>The post <a rel="nofollow" href="https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples">SwiftUI Search Bar: Best Practices and Examples</a> appeared first on <a rel="nofollow" href="https://www.swiftyplace.com">swiftyplace</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples/feed</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Search Tokens in SwiftUI: How to implement advanced search in iOS and macOS</title>
		<link>https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos</link>
					<comments>https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos#respond</comments>
		
		<dc:creator><![CDATA[Karin Prater]]></dc:creator>
		<pubDate>Wed, 01 Mar 2023 11:31:01 +0000</pubDate>
				<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[SwiftUI Components]]></category>
		<category><![CDATA[search bar SwiftUI]]></category>
		<category><![CDATA[SwiftUI components]]></category>
		<category><![CDATA[SwiftUI search]]></category>
		<category><![CDATA[SwiftUI search tokens]]></category>
		<category><![CDATA[token SwiftUI]]></category>
		<category><![CDATA[tokens SwiftUI]]></category>
		<guid isPermaLink="false">https://swiftyplace.com/?p=1052</guid>

					<description><![CDATA[<p>Learn how to use SwiftUI Search Tokens for advanced search functionality in your app. Discover how to improve your user's search experience.</p>
<p>The post <a rel="nofollow" href="https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos">Search Tokens in SwiftUI: How to implement advanced search in iOS and macOS</a> appeared first on <a rel="nofollow" href="https://www.swiftyplace.com">swiftyplace</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Introduced in iOS 16 and macOS 14, SwiftUI Search Tokens are a powerful new feature that allows developers to enhance search functionality in their apps. Search Tokens are a more advanced version of the Search Bar, allowing users to create complex search queries using tokens that represent specific search terms or categories. By using Search Tokens in your SwiftUI app, you can provide a more intuitive and customizable search experience, allowing users to easily refine their search queries and find the content they need. In this blog post, we&#8217;ll explore how to use SwiftUI Search Tokens to create advanced search functionality in your app.</p>



<p class="wp-block-paragraph">This blog is an addition to my previous post about&nbsp;<a href="https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples" target="_blank" rel="noopener">SwiftUI Search Bar: Best Practices and Examples</a>. Please have a look there if you need an overview of the SwiftUI search.</p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://school.swiftyplace.com/17130/files/63ff01d7aec84_1677656535_searchexampleapp.zip" target="_blank" rel="noopener">Download the project files</a></p>



<div style="height:28px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Search Tokens</h2>



<p class="wp-block-paragraph">Let&#8217;s say I have a food delivery app and I&nbsp;want to allow the user of my app to search for food that is on the go or on sale. My data has a tag property that I can use to filter.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="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]
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Meal</span><span style="color: #F6F6F4">: Codable, Hashable, Identifiable {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> imageURL: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> id: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> category: MealCategory</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> name: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> location: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> rating: </span><span style="color: #97E1F1; font-style: italic">Double</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> tags: [</span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4">]</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:14px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">But how do I allow my users to pick these options? You might want to use a picker, but tokens are a much smoother user experience.</p>



<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-tokens@2x.webp" alt="swiftui search tokens on iOS example"/></figure>



<div style="height:28px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">There are 2 different kinds of implementation for tokens:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="searchable(text: Binding&lt;String&gt;, tokens: Binding&lt;C&gt;, suggestedTokens: Binding&lt;C&gt;, @ViewBuilder token: @escaping (C.Element)

searchable(text: Binding&lt;String&gt;, tokens: Binding&lt;C&gt;, @ViewBuilder token: @escaping (C.Element) -&gt; T)" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: Binding</span><span style="color: #F286C4">&lt;</span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: Binding</span><span style="color: #F286C4">&lt;</span><span style="color: #F6F6F4">C</span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">suggestedTokens</span><span style="color: #F6F6F4">: Binding</span><span style="color: #F286C4">&lt;</span><span style="color: #F6F6F4">C</span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4">, @</span><span style="color: #F286C4">ViewBuilder</span><span style="color: #F6F6F4"> token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> @</span><span style="color: #F286C4">escaping</span><span style="color: #F6F6F4"> (C.</span><span style="color: #97E1F1; font-style: italic">Element</span><span style="color: #F6F6F4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: Binding</span><span style="color: #F286C4">&lt;</span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: Binding</span><span style="color: #F286C4">&lt;</span><span style="color: #F6F6F4">C</span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4">, @</span><span style="color: #F286C4">ViewBuilder</span><span style="color: #F6F6F4"> token</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> @</span><span style="color: #F286C4">escaping</span><span style="color: #F6F6F4"> (C.</span><span style="color: #97E1F1; font-style: italic">Element</span><span style="color: #F6F6F4">) </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> T)</span></span></code></pre></div>



<div style="height:22px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">In the first, you give a fixed array of suggested tokens. SwiftUI displays these suggestions like in the example above. When the user selects a suggestion, it is removed from the suggestions array and added to the selected token property.</p>



<p class="wp-block-paragraph">The second implementation will not show suggestions. A use case would be to dynamically add tokens depending on the user input. For example, if you have a list of common search tokens. As soon as the user enters one of these, you add the token. Or when the user types a comma, you can use the search text as a new token.</p>



<p class="wp-block-paragraph">Let&#8217;s look at an example for the first case. We need to create a new type for tokens because SwiftUI expects an Identifiable type:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="enum MealSearchToken: String, Hashable, CaseIterable, Identifiable {
    case fourStarReview = &quot;4+ star review&quot;
    case onSale = &quot;On sale&quot;
    case toGo = &quot;To go&quot;
    case coupon = &quot;coupon&quot;
    var id: String { rawValue }
    func icon() -&gt; String {
        switch self {
            case .fourStarReview:
               return &quot;star&quot;
            case .onSale:
               return &quot;paperplane&quot;
            case .toGo:
               return &quot;figure.walk&quot;
            case .coupon:
               return &quot;tag&quot;
        }
    }
}	" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">enum</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealSearchToken</span><span style="color: #F6F6F4">: String, Hashable, CaseIterable, Identifiable {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> fourStarReview </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">4+ star review</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> onSale </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">On sale</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> toGo </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">To go</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> coupon </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">coupon</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> id: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> { rawValue }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">icon</span><span style="color: #F6F6F4">() </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">switch</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .fourStarReview</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">               </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">star</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .onSale</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">               </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">paperplane</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .toGo</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">               </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">figure.walk</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .coupon</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">               </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">tag</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}	</span></span></code></pre></div>



<div style="height:27px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">I am using here the tokens that correspond to the values of the Meal types tags property. Now, you need to create state property for the selected and suggested tokens. The filtered meal array needs to now also include the selected tokens:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = &quot;&quot;
    @Published var selectedTokens = [MealSearchToken]()
    @Published var suggestedTokens = MealSearchToken.allCases
    var filteredMeals: [Meal] {
        var meals = self.meals
        if searchText.count &gt; 0 {
            meals = meals.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
        }
        for token in selectedTokens {
            switch token {
                case .fourStarReview:
                    meals = meals.filter({ $0.rating &gt;= 4 })
                case .onSale, .coupon, .toGo:
                    meals = meals.filter({ $0.containsTag(token.rawValue)})
            }
        }
        return meals
    }
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> searchText: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedTokens </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [MealSearchToken]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> suggestedTokens </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> MealSearchToken.allCases</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredMeals: [Meal] {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.meals</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> searchText.</span><span style="color: #BF9EEE">count</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.name.</span><span style="color: #97E1F1">localizedCaseInsensitiveContains</span><span style="color: #F6F6F4">(searchText) }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> token </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> selectedTokens {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">switch</span><span style="color: #F6F6F4"> token {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .fourStarReview</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4">({ </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.rating </span><span style="color: #F286C4">&gt;=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">4</span><span style="color: #F6F6F4"> })</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .onSale, .coupon, .toGo</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                    meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4">({ </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">containsTag</span><span style="color: #F6F6F4">(token.</span><span style="color: #BF9EEE">rawValue</span><span style="color: #F6F6F4">)})</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> meals</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">I can now update my searchable modifier to include these tokens:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText,
            tokens: $viewModel.selectedTokens,
            suggestedTokens: $viewModel.suggestedTokens,
            token: { token in
                Text(token.rawValue)
            })" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText,</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: $viewModel.selectedTokens,</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">suggestedTokens</span><span style="color: #F6F6F4">: $viewModel.suggestedTokens,</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">token</span><span style="color: #F6F6F4">: { token </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(token.</span><span style="color: #BF9EEE">rawValue</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            })</span></span></code></pre></div>



<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">In the closure, I will get the token and decide what view to show. For example, if you like to add a colorful background and an icon, you can have a look at this example:</p>


<div class="gb-container gb-container-ac395c2f">
<div class="gb-container gb-container-6e11f820">

<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="import SwiftUI
struct ContentView: View {
    @StateObject var viewModel = MealTokenListViewModel()
    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.filteredMeals) { meal in
                   MealCardView(meal: meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle(&quot;Find Your Next Meal&quot;)
            .searchable(text: $viewModel.searchText,
                        tokens: $viewModel.selectedTokens,
                        suggestedTokens: $viewModel.suggestedTokens,
                        token: { token in
                Label(token.rawValue, systemImage: token.icon())
                    .encapsulate(color: Color.cyan,
                                 foregroundColor: .white)
            })
        }
    }
}	" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">SwiftUI</span></span>
<span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealTokenListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            List {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredMeals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText,</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: $viewModel.selectedTokens,</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">suggestedTokens</span><span style="color: #F6F6F4">: $viewModel.suggestedTokens,</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">token</span><span style="color: #F6F6F4">: { token </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Label</span><span style="color: #F6F6F4">(token.</span><span style="color: #BF9EEE">rawValue</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">systemImage</span><span style="color: #F6F6F4">: token.</span><span style="color: #97E1F1">icon</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">                    .</span><span style="color: #97E1F1">encapsulate</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">color</span><span style="color: #F6F6F4">: Color.cyan,</span></span>
<span class="line"><span style="color: #F6F6F4">                                 </span><span style="color: #97E1F1">foregroundColor</span><span style="color: #F6F6F4">: .white)</span></span>
<span class="line"><span style="color: #F6F6F4">            })</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}	</span></span></code></pre></div>

</div>

<div class="gb-container gb-container-d70e39a3">

<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-token-encapsulate-539x1024.jpg" alt=""/></figure>

</div>
</div>


<div style="height:24px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Showing different views for suggested and selected tokens</h2>



<p class="wp-block-paragraph">The provided view is used in both the suggestion list and as a token in the text field. There is a way to add 2 different views. For my example, I want a shorter token in the text field otherwise it does not fit more than 2 tokens. First, I don´t use the <strong>suggestedTokens </strong>parameter and use a shorter view for the token. This will be used inside the search text field:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code=".searchable(text: $viewModel.searchText,
            tokens: $viewModel.selectedTokens, token: { token in
            Label(&quot; &quot;, systemImage: token.icon())
})" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">.</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText,</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: $viewModel.selectedTokens, </span><span style="color: #97E1F1">token</span><span style="color: #F6F6F4">: { token </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">Label</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98"> </span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">systemImage</span><span style="color: #F6F6F4">: token.</span><span style="color: #97E1F1">icon</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">})</span></span></code></pre></div>



<div style="height:29px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Then I use the <strong>searchSuggestions</strong> modifier to add the tokens in the suggestions area:</p>


<div class="gb-container gb-container-54e2b013">
<div class="gb-container gb-container-ee6127a0">

<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="struct ContentView: View {
    @StateObject var viewModel = MealTokenListViewModel()
    @State var selectedMeal: Meal? = nil
    var body: some View {
        NavigationStack {
            List {
                ForEach(viewModel.filteredMeals) { meal in
                   MealCardView(meal: meal)
                }
                .listRowSeparator(.hidden, edges: .all)
            }
            .listStyle(.plain)
            .navigationTitle(&quot;Find Your Next Meal&quot;)
            .searchable(text: $viewModel.searchText,
                        tokens: $viewModel.selectedTokens, token: { token in
                Label(&quot; &quot;, systemImage:  token.icon())
            })
            .searchSuggestions({
                ForEach(viewModel.suggestedTokens) { token in
                    Button {
                        viewModel.selectedTokens.append(token)
                    } label: {
                        Label(token.rawValue, systemImage:  token.icon())
                    }
                }
            })
        }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">ContentView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealTokenListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@State</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">nil</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">            List {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredMeals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText,</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: $viewModel.selectedTokens, </span><span style="color: #97E1F1">token</span><span style="color: #F6F6F4">: { token </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Label</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98"> </span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">systemImage</span><span style="color: #F6F6F4">:  token.</span><span style="color: #97E1F1">icon</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">            })</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">searchSuggestions</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.suggestedTokens) { token </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                    Button {</span></span>
<span class="line"><span style="color: #F6F6F4">                        viewModel.selectedTokens.</span><span style="color: #97E1F1">append</span><span style="color: #F6F6F4">(token)</span></span>
<span class="line"><span style="color: #F6F6F4">                    } label</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #97E1F1">Label</span><span style="color: #F6F6F4">(token.</span><span style="color: #BF9EEE">rawValue</span><span style="color: #F6F6F4">, </span><span style="color: #97E1F1">systemImage</span><span style="color: #F6F6F4">:  token.</span><span style="color: #97E1F1">icon</span><span style="color: #F6F6F4">())</span></span>
<span class="line"><span style="color: #F6F6F4">                    }</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">            })</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>

</div>

<div class="gb-container gb-container-23093d3d">

<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-token-custom-appearance-532x1024.jpg" alt=""/></figure>

</div>
</div>


<div style="height:31px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">It does not work well with icons only. But you could for example also show a shorter text, that describes the search token well.</p>



<div style="height:51px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Dynamically Add Search Tokens</h2>



<p class="wp-block-paragraph">I want to show you now a more advanced implementation. You can analyze the search text during typing and add new tokens dynamically. For example, if you have a list of common search tokens. As soon as the user enters one of these and presses enter or comma, you add the token.</p>



<p class="wp-block-paragraph">I will handle the token updates in the view model. The following code does not show token suggestions:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="NavigationStack {
    List {
        ...
    }
    .searchable(text: $viewModel.searchText,
                tokens: $viewModel.selectedTokens, token: { token in
        Text(token.name)
    })
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F6F6F4">NavigationStack {</span></span>
<span class="line"><span style="color: #F6F6F4">    List {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText,</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">tokens</span><span style="color: #F6F6F4">: $viewModel.selectedTokens, </span><span style="color: #97E1F1">token</span><span style="color: #F6F6F4">: { token </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(token.name)</span></span>
<span class="line"><span style="color: #F6F6F4">    })</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">As I said previously, the token data needs to be Identifiable. I am declaring a simple type, that holds the String values and add a test data array that I will use as common tokens:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="struct StringToken: Identifiable {
    let name: String
    let id = UUID()
    static func testData() -&gt; [StringToken] {
        [&quot;Muffin&quot;, &quot;Noodles&quot;, &quot;Beef&quot;, &quot;Wraps&quot;, &quot;Hamburger&quot;, &quot;Chicken&quot;,
         &quot;Falafel&quot;, &quot;Pita&quot;, &quot;Avocado&quot;, &quot;Tomato&quot;,
         &quot;Chocolate&quot;, &quot;Strawberry&quot;, &quot;Coffee&quot;, &quot;Cheese&quot;].map {
            StringToken(name: $0)
        }
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">StringToken</span><span style="color: #F6F6F4">: Identifiable {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> name: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> id </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">UUID</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">static</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">func</span><span style="color: #F6F6F4"> </span><span style="color: #62E884">testData</span><span style="color: #F6F6F4">() </span><span style="color: #F286C4">-&gt;</span><span style="color: #F6F6F4"> [StringToken] {</span></span>
<span class="line"><span style="color: #F6F6F4">        [</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Muffin</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Noodles</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Beef</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Wraps</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Hamburger</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Chicken</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">         </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Falafel</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Pita</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Avocado</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Tomato</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">,</span></span>
<span class="line"><span style="color: #F6F6F4">         </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Chocolate</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Strawberry</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Coffee</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">, </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Cheese</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">].</span><span style="color: #97E1F1">map</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">StringToken</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">name</span><span style="color: #F6F6F4">: </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">Now to the hard part of updating the view model. First I add properties for StringToken and the common tokens which in a real app would come from the server.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = &quot;&quot;
    @Published var selectedTokens = [StringToken]()
    @Published private var commonTokens: [StringToken] = StringToken.testData()
    var filteredMeals: [Meal] {
        var meals = self.meals
        if searchText.count &gt; 0 {
            meals = meals.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
        }
        if selectedTokens.count &gt; 0 {
            let tokens = selectedTokens.map { $0.name }
            meals = meals.filter { $0.name.containsAll(tokens) }
        }
        return meals
    }
    ...
}    " style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> searchText: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedTokens </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [StringToken]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">private</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> commonTokens: [StringToken] </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> StringToken.</span><span style="color: #97E1F1">testData</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredMeals: [Meal] {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.meals</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> searchText.</span><span style="color: #BF9EEE">count</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.name.</span><span style="color: #97E1F1">localizedCaseInsensitiveContains</span><span style="color: #F6F6F4">(searchText) }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> selectedTokens.</span><span style="color: #BF9EEE">count</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> tokens </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> selectedTokens.</span><span style="color: #97E1F1">map</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.name }</span></span>
<span class="line"><span style="color: #F6F6F4">            meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.name.</span><span style="color: #97E1F1">containsAll</span><span style="color: #F6F6F4">(tokens) }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> meals</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}    </span></span></code></pre></div>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">In order to create new tokens dynamically, I need to check when the search text changes. Time to use the Combine framework.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="import SwiftUI
import Combine
class MealListViewModel: ObservableObject {
   ...
   var subscriptions = Set&lt;AnyCancellable&gt;()
   init() {
        meals = service.loadMealsFromDataStorage()
        $searchText.filter({
            $0.last == &quot; &quot;
        })
        .filter({
            $0.count &gt; 1
        })
        .sink { [unowned self] text in
            var filteredText = text
            for suggestion in commonTokens {
                if let range = filteredText.lowercased().range(of: suggestion.name.lowercased()) {
                    filteredText.removeSubrange(range)
                    filteredText.removeLast() // remove &quot; &quot;
                    DispatchQueue.main.async {
                        self.searchText = filteredText
                    }
                    self.selectedTokens.append(suggestion)
                }
            }
        }
        .store(in: &amp;subscriptions)
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">SwiftUI</span></span>
<span class="line"><span style="color: #F286C4">import</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Combine</span></span>
<span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">   </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">   </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> subscriptions </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Set</span><span style="color: #F286C4">&lt;</span><span style="color: #F6F6F4">AnyCancellable</span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">   </span><span style="color: #F286C4">init</span><span style="color: #F6F6F4">() {</span></span>
<span class="line"><span style="color: #F6F6F4">        meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> service.</span><span style="color: #97E1F1">loadMealsFromDataStorage</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">        $searchText.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.</span><span style="color: #BF9EEE">last</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98"> </span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.</span><span style="color: #BF9EEE">count</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">        .sink { [</span><span style="color: #F286C4">unowned</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">] text </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredText </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> text</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">for</span><span style="color: #F6F6F4"> suggestion </span><span style="color: #F286C4">in</span><span style="color: #F6F6F4"> commonTokens {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> range </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> filteredText.</span><span style="color: #97E1F1">lowercased</span><span style="color: #F6F6F4">().</span><span style="color: #BF9EEE">range</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">of</span><span style="color: #F6F6F4">: suggestion.name.</span><span style="color: #97E1F1">lowercased</span><span style="color: #F6F6F4">()) {</span></span>
<span class="line"><span style="color: #F6F6F4">                    filteredText.</span><span style="color: #97E1F1">removeSubrange</span><span style="color: #F6F6F4">(range)</span></span>
<span class="line"><span style="color: #F6F6F4">                    filteredText.</span><span style="color: #97E1F1">removeLast</span><span style="color: #F6F6F4">() </span><span style="color: #7B7F8B">// remove &quot; &quot;</span></span>
<span class="line"><span style="color: #F6F6F4">                    DispatchQueue.main.async {</span></span>
<span class="line"><span style="color: #F6F6F4">                        </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.searchText </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> filteredText</span></span>
<span class="line"><span style="color: #F6F6F4">                    }</span></span>
<span class="line"><span style="color: #F6F6F4">                    </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.selectedTokens.</span><span style="color: #97E1F1">append</span><span style="color: #F6F6F4">(suggestion)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">store</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">in</span><span style="color: #F6F6F4">: </span><span style="color: #F286C4">&amp;</span><span style="color: #F6F6F4">subscriptions)</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:26px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">First, I check if the user&#8217;s last input was an &#8221; &#8221; with a filter. Then I analyze the search text in the sink. I loop over all common tokens and check if any of these is contained in the user input. If it is, I remove the token from the search text. I create a new token and add it to the selected tokens.</p>



<p class="wp-block-paragraph">In order to update the search text state property, I need to use <strong>DispatchQueue.main.async,</strong> otherwise the SwiftUI view will not update correctly. This seems to be an odd bug.</p>



<p class="wp-block-paragraph">A similar data stream can be implemented if you want to create new tokens when the user types a comma &#8220;,&#8221;. In this case, I simply take the whole search term and create a new token from it.</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
       ...
       init() {
       ...
       $searchText.filter({
            $0.last == &quot;,&quot;
        })
        .filter({
            $0.count &gt; 1
        })
        .sink { text in
            var filteredText = text
            filteredText.removeLast() // remove &quot;,&quot; at the end
            self.selectedTokens.append(StringToken(name: filteredText))
            DispatchQueue.main.async {
                self.searchText = &quot;&quot;
            }
        }
        .store(in: &amp;subscriptions)
    }
}		" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">       </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">       </span><span style="color: #F286C4">init</span><span style="color: #F6F6F4">() {</span></span>
<span class="line"><span style="color: #F6F6F4">       </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">       $searchText.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.</span><span style="color: #BF9EEE">last</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">,</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4">({</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.</span><span style="color: #BF9EEE">count</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">1</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">        .sink { text </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredText </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> text</span></span>
<span class="line"><span style="color: #F6F6F4">            filteredText.</span><span style="color: #97E1F1">removeLast</span><span style="color: #F6F6F4">() </span><span style="color: #7B7F8B">// remove &quot;,&quot; at the end</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.selectedTokens.</span><span style="color: #97E1F1">append</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">StringToken</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">name</span><span style="color: #F6F6F4">: filteredText))</span></span>
<span class="line"><span style="color: #F6F6F4">            DispatchQueue.main.async {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.searchText </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">store</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">in</span><span style="color: #F6F6F4">: </span><span style="color: #F286C4">&amp;</span><span style="color: #F6F6F4">subscriptions)</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}		</span></span></code></pre></div>



<div style="height:25px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-tokens-dynamically@2x.webp" alt="Swiftui search example with tokens that are added dynamically when the user types"/><figcaption class="wp-element-caption">When the user types, the search text is analyzed and new tokens are added dynamically when a common token is used or the user enters &#8220;,&#8221;.</figcaption></figure>



<div style="height:32px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Other Filter Options</h2>



<p class="wp-block-paragraph">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:<a href="https://www.swiftyplace.com/blog/swiftui-picker-made-easy-tutorial-with-example" target="_blank" rel="noopener"> &#8220;Master&nbsp;SwiftUI Picker View&#8221;</a>. For example, you could add a picker or menu view in the toolbar. The picker options would be similar to the tokens.</p>



<div style="height:37px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">By using SwiftUI Search Tokens, you can create a more advanced and customizable search experience in your app. You can add tokens to the search bar by specifying a list of token strings, and show them in the suggested area to help guide users in their search. Additionally, you can dynamically add new tokens by analyzing the search text as the user types, allowing for more personalized and accurate search results. With these features, SwiftUI Search Tokens provide a powerful tool for enhancing the search functionality of your app.</p>



<p class="wp-block-paragraph"><strong>Further Reading:</strong></p>



<ul class="wp-block-list">
<li>Use Search with Core Data: <a href="https://www.swiftyplace.com/courses/swiftui-and-core-data" target="_blank" rel="noopener">SwiftUI and Core Data Course</a></li>



<li><a href="https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples" target="_blank" rel="noopener">How to work with the&nbsp;Search bar in SwiftUI</a></li>



<li><a href="https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos" target="_blank" rel="noopener">Search Scope for advanced search queries</a>&nbsp;&#8211; a must for macOS apps</li>



<li><a href="https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/search-fields/" target="_blank" rel="noopener">Human interface guidelines for search</a></li>
</ul>
<p>The post <a rel="nofollow" href="https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos">Search Tokens in SwiftUI: How to implement advanced search in iOS and macOS</a> appeared first on <a rel="nofollow" href="https://www.swiftyplace.com">swiftyplace</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>How to use Search Scope in SwiftUI to improve search on iOS and macOS</title>
		<link>https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos</link>
					<comments>https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos#respond</comments>
		
		<dc:creator><![CDATA[Karin Prater]]></dc:creator>
		<pubDate>Wed, 01 Mar 2023 11:14:40 +0000</pubDate>
				<category><![CDATA[SwiftUI]]></category>
		<category><![CDATA[SwiftUI Components]]></category>
		<category><![CDATA[search macOS]]></category>
		<category><![CDATA[search scope]]></category>
		<category><![CDATA[SwiftUI macOS]]></category>
		<category><![CDATA[SwiftUI search]]></category>
		<guid isPermaLink="false">https://swiftyplace.com/?p=1049</guid>

					<description><![CDATA[<p>Discover how to enhance search functionality on iOS and macOS by using Search Scopes in SwiftUI. </p>
<p>The post <a rel="nofollow" href="https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos">How to use Search Scope in SwiftUI to improve search on iOS and macOS</a> appeared first on <a rel="nofollow" href="https://www.swiftyplace.com">swiftyplace</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">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&#8217;s ability to find the content they need quickly and efficiently. In this blog post, we&#8217;ll explore how to use Search Scopes in SwiftUI to enhance search functionality on iOS and macOS.&nbsp;</p>



<p class="wp-block-paragraph">Search Scope is available for iOS 16+ and macOS 13+.</p>



<p class="wp-block-paragraph">This blog is an addition to my previous post about <a href="https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples" target="_blank" rel="noopener">SwiftUI Search Bar: Best Practices and Examples</a>. Please have a look there if you need an overview of the SwiftUI search.</p>



<p class="wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2b07.png" alt="⬇" class="wp-smiley" style="height: 1em; max-height: 1em;" /> <a href="https://github.com/gahntpo/SearchExampleApp" target="_blank" rel="noopener"><strong>Download the project files</strong></a></p>



<div style="height:34px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Defining a Search Scope</h2>



<p class="wp-block-paragraph">As an example, I will continue using a Meal app that shows a list of meals. The Meal data is defined as follows:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="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]
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">Meal</span><span style="color: #F6F6F4">: Codable, Hashable, Identifiable {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> imageURL: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> id: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> category: MealCategory</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> name: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> location: </span><span style="color: #97E1F1; font-style: italic">String</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> rating: </span><span style="color: #97E1F1; font-style: italic">Double</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> tags: [</span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4">]</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:27px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">The category is defined as follows:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="enum MealCategory: String, Codable, CaseIterable, Identifiable, Hashable {
    case african = &quot;African&quot;
    case american = &quot;American&quot;
    case asian = &quot;Asian&quot;
    case europian = &quot;Europian&quot;
    case oceanian = &quot;Oceanian&quot;
    var id: String { rawValue }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">enum</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealCategory</span><span style="color: #F6F6F4">: String, Codable, CaseIterable, Identifiable, Hashable {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> african </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">African</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> american </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">American</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> asian </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Asian</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> europian </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Europian</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> oceanian </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Oceanian</span><span style="color: #DEE492">&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> id: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> { rawValue }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:27px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">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:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="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(&quot;Find Your Next Meal&quot;)
        }, detail: {
            if let meal = selectedMeal {
                DetailView(meal: meal)
            } else {
                Text(&quot;Select a Meal&quot;)
            }
        })
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">MealListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@State</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">nil</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">NavigationSplitView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">sidebar</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">List</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selection</span><span style="color: #F6F6F4">: $selectedMeal) {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.filteredMeals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                        .</span><span style="color: #97E1F1">tag</span><span style="color: #F6F6F4">(meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        }, </span><span style="color: #97E1F1">detail</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> meal </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> selectedMeal {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">DetailView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">            } </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Select a Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:38px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">How to add a Search Scope</h2>



<p class="wp-block-paragraph">In order to add a search scope, you can simply use the <strong>searchScopes</strong> modifier where you have to provide a binding to an Identifiable type used for selecting the scope.&nbsp;</p>



<p class="wp-block-paragraph">I am storing my state property for the selected scope in the view model, where I also will evaluate the filtered meals array:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = &quot;&quot;
    @Published var mealSearchScope: MealSearchScope = .all
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> searchText: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> mealSearchScope: MealSearchScope </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> .all</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:27px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">In this case, I show all options for my scope category in a ForEach. The implementation is similar to a SwiftUI picker view.&nbsp;</p>


<div class="gb-container gb-container-8da49027">
<div class="gb-container gb-container-d6c62277">

<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="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(&quot;Find Your Next Meal&quot;)
        }, detail: {
            if let meal = selectedMeal {
                DetailView(meal: meal)
            } else {
                Text(&quot;Select a Meal&quot;)
            }
        })
        .searchable(text: $viewModel.searchText)
        .searchScopes($viewModel.mealSearchScope, scopes: {
            Text(&quot;All&quot;).tag(MealSearchScope.all)
            ForEach(MealCategory.allCases) { category in
                Text(category.rawValue)
                     .tag(MealSearchScope.category(category))
            }
        })
    }
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">struct</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1; font-style: italic">SearchScopeMealListView</span><span style="color: #F6F6F4">: View {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@StateObject</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> viewModel </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealTokenListViewModel</span><span style="color: #F6F6F4">()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@State</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> selectedMeal: Meal</span><span style="color: #F286C4">?</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">nil</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> body: </span><span style="color: #F286C4">some</span><span style="color: #F6F6F4"> View {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #97E1F1">NavigationSplitView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">sidebar</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">List</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">selection</span><span style="color: #F6F6F4">: $selectedMeal) {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(viewModel.meals) { meal </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                   </span><span style="color: #97E1F1">MealCardView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                        .</span><span style="color: #97E1F1">tag</span><span style="color: #F6F6F4">(meal)</span></span>
<span class="line"><span style="color: #F6F6F4">                }</span></span>
<span class="line"><span style="color: #F6F6F4">                .</span><span style="color: #97E1F1">listRowSeparator</span><span style="color: #F6F6F4">(.hidden, </span><span style="color: #97E1F1">edges</span><span style="color: #F6F6F4">: .all)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">listStyle</span><span style="color: #F6F6F4">(.plain)</span></span>
<span class="line"><span style="color: #F6F6F4">            .</span><span style="color: #97E1F1">navigationTitle</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Find Your Next Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">        }, </span><span style="color: #97E1F1">detail</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> meal </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> selectedMeal {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">DetailView</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">meal</span><span style="color: #F6F6F4">: meal)</span></span>
<span class="line"><span style="color: #F6F6F4">            } </span><span style="color: #F286C4">else</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">Select a Meal</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">searchable</span><span style="color: #F6F6F4">(</span><span style="color: #97E1F1">text</span><span style="color: #F6F6F4">: $viewModel.searchText)</span></span>
<span class="line"><span style="color: #F6F6F4">        .</span><span style="color: #97E1F1">searchScopes</span><span style="color: #F6F6F4">($viewModel.mealSearchScope, </span><span style="color: #97E1F1">scopes</span><span style="color: #F6F6F4">: {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(</span><span style="color: #DEE492">&quot;</span><span style="color: #E7EE98">All</span><span style="color: #DEE492">&quot;</span><span style="color: #F6F6F4">).</span><span style="color: #97E1F1">tag</span><span style="color: #F6F6F4">(MealSearchScope.all)</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #97E1F1">ForEach</span><span style="color: #F6F6F4">(MealCategory.allCases) { category </span><span style="color: #F286C4">in</span></span>
<span class="line"><span style="color: #F6F6F4">                </span><span style="color: #97E1F1">Text</span><span style="color: #F6F6F4">(category.</span><span style="color: #BF9EEE">rawValue</span><span style="color: #F6F6F4">)</span></span>
<span class="line"><span style="color: #F6F6F4">                     .</span><span style="color: #97E1F1">tag</span><span style="color: #F6F6F4">(MealSearchScope.</span><span style="color: #97E1F1">category</span><span style="color: #F6F6F4">(category))</span></span>
<span class="line"><span style="color: #F6F6F4">            }</span></span>
<span class="line"><span style="color: #F6F6F4">        })</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>

</div>

<div class="gb-container gb-container-28ee84df">

<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-token.webp" alt="swiftui search token example"/></figure>

</div>
</div>


<div style="height:31px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">On iOS, the search scope area is shown as a picker with a segmented picker style.&nbsp;When the user first enters the search text field, no search scope is displayed. Only once you start typing, will the scope be shown.&nbsp;</p>



<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/search-scope-interaction-ios@2x.webp" alt="example for how the interaction for search scope works on iOS with SwiftUI"/></figure>



<div style="height:29px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">&nbsp;The initially selected scope is set to all because I set the initial value for the state property to all</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(1 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
     ...
    @Published var mealSearchScope: MealSearchScope = .all
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">     </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> mealSearchScope: MealSearchScope </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> .all</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:29px" aria-hidden="true" class="wp-block-spacer"></div>



<p class="wp-block-paragraph">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.</p>


<div class="gb-container gb-container-1d9457c1">

<figure class="wp-block-image"><img decoding="async" src="https://www.swiftyplace.com/wp-content/uploads/2023/08/macos_search_scope-1-1024x704.webp" alt="Example for SwiftUI search scope on macOS"/></figure>

</div>


<div style="height:29px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Showing Filtered Results</h2>



<p class="wp-block-paragraph">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:</p>



<div class="wp-block-kevinbatdorf-code-block-pro cbp-has-line-numbers" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono-NL.ttf" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono-NL,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;--cbp-line-number-color:#f6f6f4;--cbp-line-number-width:calc(2 * 0.6 * .875rem);line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="class MealListViewModel: ObservableObject {
    @Published var meals = [Meal]()
    @Published var searchText: String = &quot;&quot;
    @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 &gt; 0 {
            meals = meals.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
        }
        return meals
    }
    ...
}" style="color:#f6f6f4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M16.5 8.25V6a2.25 2.25 0 00-2.25-2.25H6A2.25 2.25 0 003.75 6v8.25A2.25 2.25 0 006 16.5h2.25m8.25-8.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-7.5A2.25 2.25 0 018.25 18v-1.5m8.25-8.25h-6a2.25 2.25 0 00-2.25 2.25v6"></path></svg></span><pre class="shiki dracula-soft" style="background-color: #282A36" tabindex="0"><code><span class="line"><span style="color: #F286C4">class</span><span style="color: #F6F6F4"> </span><span style="color: #97E1F1">MealListViewModel</span><span style="color: #F6F6F4">: ObservableObject {</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> [Meal]()</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> searchText: </span><span style="color: #97E1F1; font-style: italic">String</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #DEE492">&quot;&quot;</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">@Published</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> mealSearchScope: MealSearchScope </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> .all</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> filteredMeals: [Meal] {</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">var</span><span style="color: #F6F6F4"> meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE; font-style: italic">self</span><span style="color: #F6F6F4">.meals</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">switch</span><span style="color: #F6F6F4"> mealSearchScope {</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .all</span><span style="color: #F286C4">:</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">break</span></span>
<span class="line"><span style="color: #F6F6F4">            </span><span style="color: #F286C4">case</span><span style="color: #F6F6F4"> .</span><span style="color: #97E1F1">category</span><span style="color: #F6F6F4">(</span><span style="color: #F286C4">let</span><span style="color: #F6F6F4"> category)</span><span style="color: #F286C4">:</span></span>
<span class="line"><span style="color: #F6F6F4">                meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.category </span><span style="color: #F286C4">==</span><span style="color: #F6F6F4"> category }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">if</span><span style="color: #F6F6F4"> searchText.</span><span style="color: #BF9EEE">count</span><span style="color: #F6F6F4"> </span><span style="color: #F286C4">&gt;</span><span style="color: #F6F6F4"> </span><span style="color: #BF9EEE">0</span><span style="color: #F6F6F4"> {</span></span>
<span class="line"><span style="color: #F6F6F4">            meals </span><span style="color: #F286C4">=</span><span style="color: #F6F6F4"> meals.</span><span style="color: #97E1F1">filter</span><span style="color: #F6F6F4"> { </span><span style="color: #BF9EEE; font-style: italic">$0</span><span style="color: #F6F6F4">.name.</span><span style="color: #97E1F1">localizedCaseInsensitiveContains</span><span style="color: #F6F6F4">(searchText) }</span></span>
<span class="line"><span style="color: #F6F6F4">        }</span></span>
<span class="line"><span style="color: #F6F6F4">        </span><span style="color: #F286C4">return</span><span style="color: #F6F6F4"> meals</span></span>
<span class="line"><span style="color: #F6F6F4">    }</span></span>
<span class="line"><span style="color: #F6F6F4">    </span><span style="color: #F286C4">...</span></span>
<span class="line"><span style="color: #F6F6F4">}</span></span></code></pre></div>



<div style="height:38px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Other Filter Options</h2>



<p class="wp-block-paragraph">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:<a href="https://www.swiftyplace.com/blog/swiftui-picker-made-easy-tutorial-with-example" target="_blank" rel="noopener"> &#8220;Master SwiftUI Picker View&#8221;</a>. For example, you could add a picker or menu view in the toolbar. The picker options would be similar to the tokens.</p>



<div style="height:29px" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading">Summary</h2>



<p class="wp-block-paragraph">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.</p>



<p class="wp-block-paragraph"><strong>Further Reading:</strong></p>



<ul class="wp-block-list">
<li>Use Search with Core Data: <a href="https://www.swiftyplace.com/courses/swiftui-and-core-data" target="_blank" rel="noopener">SwiftUI and Core Data Course</a></li>



<li><a href="https://www.swiftyplace.com/blog/swiftui-search-bar-best-practices-and-examples" target="_blank" rel="noopener">How to work with the Search bar in SwiftUI</a></li>



<li><a href="https://www.swiftyplace.com/blog/search-tokens-swiftui-how-to-implement-advanced-search-in-ios-and-macos" target="_blank" rel="noopener">Search Tokens for advanced search queries</a> &#8211; a must for macOS apps</li>



<li><a href="https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/search-fields/" target="_blank" rel="noopener">Human interface guidelines for search</a></li>
</ul>
<p>The post <a rel="nofollow" href="https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos">How to use Search Scope in SwiftUI to improve search on iOS and macOS</a> appeared first on <a rel="nofollow" href="https://www.swiftyplace.com">swiftyplace</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.swiftyplace.com/blog/how-to-use-search-scopes-in-swiftui-to-improve-search-on-ios-and-macos/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: www.swiftyplace.com @ 2026-06-15 03:28:58 by W3 Total Cache
-->