> For the complete documentation index, see [llms.txt](https://docs.mapbox.com/help/llms.txt)

# Add Location Search to an iOS app

This tutorial will walk you through how to add location search to your iOS app using Mapbox's [**Search SDK for iOS**](https://docs.mapbox.com/ios/search/guides/). This will allow a user to search for places, addresses and points of interest, and categories like restaurants, gas stations, etc. Your final app will include a search input field that provides autocomplete suggestions as users type, and displays selected locations on the map with markers.

### What we'll cover

-   Installing the **Search SDK for iOS** and setting up your secret token
-   Adding location permissions and marker assets to your project
-   Creating a main activity with Maps SDK integration
-   Building a search input component using SwiftUI
-   Implementing autocomplete suggestions to display search results
-   Handling suggestion selection and map interactions
-   Adding markers to the map for selected locations
-   Enhancing search with proximity and bounding box parameters

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-search-location-full-demo-be2705965c276fd686baa5f47aade8e2.mp4).

If you would like to run the finished product locally before following the tutorial, you can find the source code for this tutorial on [GitHub](https://github.com/mapbox/tutorials/).

Get ready to start building! This tutorial is designed to be completed in about 30 minutes.

## Prerequisites

To follow along with this guide you'll need:

-   **A Mapbox account**: [Sign up](https://account.mapbox.com/auth/signup/) or [login](https://console.mapbox.com/) to a free account
-   **Familiarity with iOS development**: Beginner experience with Swift, SwiftUI and iOS Development.
-   **A project with the Search SDK installed**: Follow the [Getting Started with the Maps Search for iOS](https://docs.mapbox.com/ios/search/guides/install/) guide.
-   **Xcode**: Version 16.2 or later

This tutorial assumes you have a basic understanding of SwiftUI and iOS development and requires you to have setup the **Mapbox Maps SDK for iOS** project as covered in the [Getting Started with Mapbox on iOS](https://docs.mapbox.com/ios/search/guides/install/) guide. That guide walks you through adding the **Mapbox Maps SDK for iOS** dependency and adding your public access token to your project's `info.plist` file.

While it's not required to use the **Maps SDK for iOS** to use the **Search SDK for iOS** a common pattern is to display a map and show an interactive search, where the user can move the map and fly to searched locations. This has many different use cases, such as searching for nearby restaurants, gas stations, or other points of interest based on the user's current location or a specific area on the map.

The Getting Started guide should leave you with a project open in Xcode with a globe displayed.

Your `ContentView` file should look like this:

```swift
import SwiftUI
import MapboxMaps

struct ContentView: View {
    var body: some View {
        let center = CLLocationCoordinate2D(latitude: 39.5, longitude: -98.0)
        Map(initialViewport: .camera(center: center, zoom: 2, bearing: 0, pitch: 0))
            .ignoresSafeArea()
    }
}
```

## Create your secret token & add the Search SDK

Before you can use location search in your iOS app, you'll need to add the **Mapbox Search SDK** to your project. A secret token is required to download the **Mapbox Search SDK**.

### Step 1: Create a secret token

A secret access token can enable access to various products/services at Mapbox, including the ability to download an SDK. To allow download access to an SDK, follow these steps:

1.  From your account's [tokens page](https://console.mapbox.com/account/access-tokens/), click the **Create a token** button.
2.  From the token creation page, give your token a name and make sure the box next to the `Downloads:Read` scope is checked.
3.  Click the **Create token** button at the bottom of the page to create your token.
4.  The token you've created is a *secret token*, which means you will only have one opportunity to copy it somewhere secure.

> **Note (warning): Protect secret access tokens**
> 
> Store secret tokens in secure a location, outside of your project folder to make sure unauthorized users cannot find it.

### Step 2: Configure your secret token

Your secret access token is used only when downloading the SDK binaries for use in your project.

To use your secret token, you must store it in a `.netrc` file in your home directory (not your project folder). This approach helps avoid accidentally exposing your secret token by keeping it out of your application's source code.

To create a `.netrc` file follow these steps:

**Step 1:** Check if you have already created a `.netrc` file on your computer.

1.  In a terminal, go to your home directory.

```bash
$ cd ~
```

2.  Run `file .netrc` to check whether a `.netrc` file already exists. You will see the following message if there is no `.netrc` file:

```bash
$ file .netrc
.netrc: cannot open `.netrc' (No such file or directory)
```

3.  If you already have a `.netrc` file, skip the next step.

**Step 2:** Create the `.netrc` file.

-   From the same terminal, run `touch .netrc`.

**Step 3:** Open the `.netrc` file.

-   From the same terminal, run `open .netrc`.

**Step 4:** Add Mapbox credentials to the `.netrc` file

Add the following lines of text to your `.netrc` file, replacing `<INSERT SECRET ACCESS TOKEN>` with the secret access token you created in step 2:

```bash
machine api.mapbox.com
login mapbox
password <INSERT SECRET ACCESS TOKEN>
```

**Step 5:** Set `.netrc` file permissions to Read & Write for the current user

**Finder**

1.  Go to your home directory: `/Users/[CurrentUser]`.

-   If you do not see the `.netrc` file, press `Command` + `Shift` + `.` (the period key).

2.  Right click on the `.netrc`.
3.  Click `Get Info`.
4.  Scroll down to `Sharing & Permissions`.
5.  Under your current username, make sure to set `Privilege` to `Read & Write`.

**Terminal**

From the same terminal, run `chmod -R 0600 .netrc`

Step 6: Add the Search SDK using the Swift Package Manager

The Mapbox Search SDK can be consumed via Swift Package Manager (SPM). To add the Search SDK with SPM, you will need to configure your environment to download it from Mapbox. This requires a Mapbox access token with the `Downloads:Read` scope. In a previous step, you added this token to your `.netrc` file.

1.  Open your Xcode project or workspace, then go to **File** > **Swift Packages** > **Add Package Dependency**.
2.  Enter `https://github.com/mapbox/search-ios.git` as the URL, press `Enter` to pull in the package, and click **Next**.
3.  Set **Version** to **Exact** and enter `2.9.0-rc.2`. Click **Next**.
4.  Once Xcode finishes fetching and checking out all the dependencies, select the **Mapbox Search UI** library (or **Mapbox Search** if you don't need any UI components). Click **Next**.
5.  In your code, you can now `import MapboxSearch` as well as any of the other packages that were downloaded as dependencies.

> **Note (warning): Troubleshooting**
> 
> -   If you need to update your packages, you can click on **File** > **Swift Packages** > **Update To Latest Package Versions**.
> -   Sometimes, artifacts cannot be resolved or errors can occur, in this case select **File** > **Swift Packages** > **Reset Package Cache**.
> -   If you see a crash in Xcode, delete your derived data folder.

## Add required permissions & marker assets

### Location permissions

Your app needs location permissions to provide better search results based on the user's current location.

Add the following permissions to your `Info.plist` file, if you don't already have them. This allows your app to temporarily access the user's location:

```xml
// highlight-start
<key>NSLocationWhenInUseUsageDescription</key>
<string>Your precise location is used to calculate turn-by-turn directions, show your location on the map, and help improve the map.</string>
// highlight-end
```

If necessary for your application, you can request permanent location access by setting `NSLocationAlwaysAndWhenInUseUsageDescription` instead of `NSLocationWhenInUseUsageDescription`, and if you want more exact location access, you can also add `LocationAccuracyAuthorizationDescription` to your `info.plist`.

### Add marker assets to Assets

First start by downloading and unzipping this `markers.zip` file.

[Download marker.png](https://docs.mapbox.com/help/help/data/map_marker.png)

This download contains a `*.png` marker file you will use to display the selected search result on the map in the next step.

In your Xcode project, click on **Assets** in the project hierarchy and drag the `map_marker.png` file into Assets.

Next you'll add code in your ContentView to wrap your Map in a MapReader to help access underlying features of the map.

## Wrap the Map in a MapReader

The `ContentView` file will host both the map and the search interface. It will manage the state between search results and map display & interactions. This will enable you to update the viewport's state and allow the Map to respond to the state changes. For example, this would allow the map to move on a successful search result and hover the map over that point of interest.

Replace the contents of your `ContentView` file with the following code.

This code encapsulates your Map inside a [`MapReader`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/mapreader/) to allow access to different functionality like changing the map style.

```swift
// Import necessary frameworks for the app
import SwiftUI          // For the user interface
import MapboxMaps       // For map rendering and interaction

struct ContentView: View {
    // Viewport controls the map's camera position (center, zoom level)
    // Starting with a view of the continental United States
    @State var viewport: Viewport = .camera(
        center: CLLocationCoordinate2D(
            latitude: 39.5,
            longitude: -98.0
        ),
        zoom: 2)
    
    var body: some View {
        ZStack {
            // Map takes full screen as the base layer
            MapReader { proxy in
                Map(viewport: $viewport) {
                    
                }
                .mapStyle(.standard())  // Use the Mapbox Standard style
            }
        }
    }
}
```

Next you'll create the search component.

## Add the Search Input

The search screen component handles the user interface for location search, including the input field, autocomplete suggestions, a bounding box to limit searches and result selection.

> **Note (warning): Search SDK UI Components**
> 
> The **Search SDK for iOS** provides a set of UI components for building search interfaces, including input fields, suggestion lists, and result displays in the UIKit paradigm. You can learn more about these components in the [Search SDK for iOS documentation](https://docs.mapbox.com/ios/search/guides/).
> 
> This tutorial covers building a custom search UI using SwiftUI functions.

Add a new swift file called `SearchScreen.swift` and add the following code:

Title: `SearchScreen.swift`

```swift

// Import necessary frameworks
import SwiftUI        // For the user interface components
import MapboxSearch   // For location search and autocomplete functionality

struct SearchScreen: View {
  // State to track the current search input text
  @State private var searchText: String = ""
  
  // Array to store search suggestions returned from Mapbox Search API
  @State private var suggestions: [PlaceAutocomplete.Suggestion] = []
  
  // Mapbox PlaceAutocomplete instance for performing searches
  @State private var placeAutocomplete = PlaceAutocomplete()
  
  
  var body: some View {
      VStack(spacing: 20) {
          // Search input field with floating card appearance
          TextField("Search for places...", text: $searchText)
              .textFieldStyle(RoundedBorderTextFieldStyle())  // Built-in rounded border style
              .background(Color.white)        // White background for visibility
              .cornerRadius(10)               // Rounded corners for modern look
              .shadow(radius: 5)              // Drop shadow for floating effect
              .padding(.horizontal)           // Horizontal padding from screen edges
              .onChange(of: searchText) { oldValue, newValue in
                  // Trigger search whenever the user types
                  performSearch(query: newValue)
              }
          
          // Display suggestions list only when we have results
          if !suggestions.isEmpty {
              List(suggestions, id: \.mapboxId) { suggestion in
                  // Each suggestion displayed as a card with name and description
                  VStack(alignment: .leading, spacing: 4) {
                      // Primary name of the location (e.g., "Central Park")
                      Text(suggestion.name)
                          .font(.headline)
                          .fontWeight(.bold)
                      
                      // Secondary description (e.g., "New York, NY, United States")
                      Text(suggestion.description ?? "")
                          .font(.caption)
                          .foregroundColor(.gray)
                  }
                  .padding(.vertical, 4)
              }
              .frame(minHeight: 0, maxHeight: 500)    // Limit height to avoid taking full screen
              .listStyle(PlainListStyle())            // Remove default list styling
              .background(Color.white)                // White background for visibility
              .cornerRadius(10)                       // Rounded corners to match search field
              .shadow(radius: 5)                      // Drop shadow for floating effect
              .padding(.horizontal)                   // Match search field padding
          }
      }
  }
  
  // Performs a search query using Mapbox Search API
  private func performSearch(query: String) {
      // Clear suggestions if search text is empty
      guard !query.isEmpty else {
          suggestions = []
          return
      }
      
      // Call Mapbox PlaceAutocomplete API with search parameters
      placeAutocomplete.suggestions(for: query) 
          { result in
          // Handle the API response
          switch result {
          case .success(let suggestionResults):
              // Update UI on main thread when we receive successful results
              DispatchQueue.main.async {
                  print("Received (suggestionResults.count) suggestions:")
                  // Debug: Print each suggestion for development purposes
                  for (index, suggestion) in suggestionResults.enumerated() {
                      print("Suggestion (index): (suggestion)")
                  }
                  // Update the suggestions array to trigger UI refresh
                  suggestions = suggestionResults
              }
              
          case .failure(let error):
              // Handle API errors (network issues, invalid API key, etc.)
              debugPrint(error)
              DispatchQueue.main.async {
                  // Clear suggestions on error
                  suggestions = []
              }
          }
      }
  }
  
}
```

### Understanding the `SearchScreen` component

This `SearchScreen.swift` component establishes the foundation for search functionality in the app. It creates a [`PlaceAutocomplete`](https://docs.mapbox.com/ios/search/guides/autocomplete/) which grabs suggestions for search results based on the user input. At present, these results are not yet displayed in the UI, but you will expose this data to user in the next step.

The search functionality triggers when users type 2 or more characters, calling [`PlaceAutocomplete.Result`](https://docs.mapbox.com/ios/search/api/core/2.9.0-rc.2/Classes/PlaceAutocomplete.html#/s:12MapboxSearch17PlaceAutocompleteC6ResultV). By default, the results only offer 10 suggestions maximum.

Details

**Deep Dive: Search Box API's Suggest & Retrieve Endpoints**

The [Search Box API](https://docs.mapbox.com/api/search/search-box/) which is defined in the `apiType` in the `createSearchEngine` method above, provides multiple endpoints for searching and retrieving location data. In this tutorial you use Search Box's `suggest` and `retrieve` endpoints, which when used together provide an interactive 2 stage search experience. First the `suggest` endpoint is called and shows search suggestions based on the input as a user types, and secondly, once the user clicks a suggestion the `retrieve` endpoint is called, returning the full information on the selected result.

For more information on this 2 stage search, see the [Search Box API documentation](https://docs.mapbox.com/api/search/search-box/#interactive-search).

The **Search SDK for iOS** used in this tutorial provides a convenient way to access these endpoints, allowing you to implement location search functionality with minimal setup.

### Import `SearchScreen.swift` into `ContentView.swift`

Now that you have created the `SearchScreen.swift` file, you can integrate it into your `ContentView.swift` file to display the search interface on top of the map.

Open your `ContentView.swift` file and add `SearchScreen` to your `View`. You'll also need to add 2 imports at the top of the file and add a reference to the `SearchScreen`, along with some global variables and a function to handle the suggestion selection.

```swift

// Import necessary frameworks for the app
import SwiftUI          // For the user interface
import MapboxMaps       // For map rendering and interaction
import MapboxSearch     // For location search functionality
import CoreLocation     // For coordinate handling


struct ContentView: View {
  // Viewport controls the map's camera position (center, zoom level)
  // Starting with a view of the continental United States
  @State var viewport: Viewport = .camera(
      center: CLLocationCoordinate2D(
          latitude: 39.5,
          longitude: -98.0
      ),
      zoom: 2)
  
  var body: some View {
      ZStack {
          // Map takes full screen as the base layer
          MapReader { proxy in
              Map(viewport: $viewport) {

              }
              //.mapStyle(.standard())  // Use the Mapbox Standard style
          }
          // Floating search interface overlaid on the map
          VStack {
              // Search component handles autocomplete and result selection
              SearchScreen()
              Spacer()  // Push search to top of screen
          }
          .padding(.top, 50)  // Add padding to avoid status bar overlap
      }
  }
}

```

Now when you run your app, you should see the search input field displayed on top of the map. Typing into the field will produce 10 search results in the UI, based on your input.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-search-location-search-results-b8f7a46fb3564494fa239e7d489ed443.mp4).

In the next steps, you will add functionality to handle user selection, allowing a user to select a result from the suggestions list.

## Handle suggestion selection and map interaction

Now that you are rendering suggestions, you need to handle user selection and update the map to add a marker at the selected location and fly the map to it.

### Update `ContentView.swift`

First you'll update `ContentView.swift` to add the needed imports, add a state variable to hold the `onSuggestionSelected`, and a function within `MapboxMap` to add a `PointAnnotation` (marker) to the map at the selected result's coordinates. And finally, you'll update the `SearchScreen` composable to pass down the `mapViewportState` and the `onSuggestionSelected` callback.

Update your `ContentView.swift` file with the highlighted code updates below:

```swift

// Import necessary frameworks for the app
import SwiftUI          // For the user interface
import MapboxMaps       // For map rendering and interaction
import MapboxSearch     // For location search functionality
import CoreLocation     // For coordinate handling

struct ContentView: View {
  // Viewport controls the map's camera position (center, zoom level)
  // Starting with a view of the continental United States
  @State var viewport: Viewport = .camera(
      center: CLLocationCoordinate2D(
          latitude: 39.5,
          longitude: -98.0
      ),
      zoom: 2)
  
  // Store the selected search result to display as a marker
  @State private var selectedResult: PlaceAutocomplete.Result?
  
  // Track the ID of the selected result to trigger map animations
  @State private var selectedResultId: String?
  
  
  var body: some View {
      ZStack {
          // Map takes full screen as the base layer
          MapReader { proxy in
              Map(viewport: $viewport) {
                  // Conditionally add a marker annotation when a location is selected
                  if let result = selectedResult, let coordinate = result.coordinate {
                      PointAnnotation(coordinate: CLLocationCoordinate2D(
                          latitude: coordinate.latitude,
                          longitude: coordinate.longitude
                      ))
                      .image(named: "map_marker")      // Use custom marker image
                      .iconSize(0.5)               // Scale the marker to half size
                      .iconAnchor(.bottom)         // Anchor point at bottom of marker
                      .iconOffset(x: 0, y: 12)    // Fine-tune marker positioning
                  }
              }
              .mapStyle(.standard())  // Use the Mapbox Standard style
          }
          
          // Floating search interface overlaid on the map
          VStack {
              // Search component handles autocomplete and result selection
              SearchScreen(onSuggestionSelected: handleSuggestionSelection)
              Spacer()  // Push search to top of screen
          }
          .padding(.top, 50)  // Add padding to avoid status bar overlap
      }
      // Monitor changes to selectedResultId to trigger map animations
      .onChange(of: selectedResultId) { oldValue, newValue in
          // When a new result is selected, animate the map to that location
          if let _ = newValue, let result = selectedResult {
              flyToLocation(result)
          }
      }
  }
  
  // Called when user selects a search suggestion
  private func handleSuggestionSelection(_ result: PlaceAutocomplete.Result) {
      selectedResult = result           // Store the selected result
      selectedResultId = result.mapboxId // Update ID to trigger map animation
  }
  
  // Animates the map camera to fly to the selected location
  private func flyToLocation(_ result: PlaceAutocomplete.Result) {
      // Ensure the result has valid coordinates
      if let coordinate = result.coordinate {
          // Convert search result coordinate to CLLocationCoordinate2D
          let mapCoordinate = CLLocationCoordinate2D(
              latitude: coordinate.latitude,
              longitude: coordinate.longitude
          )
          
          // Animate the viewport change with a smooth fly animation
          withViewportAnimation(.fly(duration: 3)) {
              viewport = .camera(
                  center: mapCoordinate,
                  zoom: 15.0  // Zoom in for detailed view of the location
              )
          }
      }
  }
}
```

### Update `SearchScreen.swift`

Next, you'll update the `SearchScreen.swift` file to accept the `onSuggestedSelected` parameter, and you'll create a new function to handle the selection of a suggestion.

Now when a user selects a suggestion from the list, the `onSuggestionSelected` callback is triggered, passing the selected `SearchResult` to the `ContentView.swift`. The `ContentView.swift` then updates the `selectedResult` state variable, which in turn adds a marker to the map at the selected location and animates the map to that location.

```swift
// Import necessary frameworks
import SwiftUI        // For the user interface components
import MapboxSearch   // For location search and autocomplete functionality

struct SearchScreen: View {
  // State to track the current search input text
  @State private var searchText: String = ""
  
  // Array to store search suggestions returned from Mapbox Search API
  @State private var suggestions: [PlaceAutocomplete.Suggestion] = []
  
  // Mapbox PlaceAutocomplete instance for performing searches
  @State private var placeAutocomplete = PlaceAutocomplete()
  
  // Callback function to notify parent view when a suggestion is selected
  let onSuggestionSelected: (PlaceAutocomplete.Result) -> Void
  
  
  var body: some View {
      VStack(spacing: 20) {
          // Search input field with floating card appearance
          TextField("Search for places...", text: $searchText)
              .textFieldStyle(RoundedBorderTextFieldStyle())  // Built-in rounded border style
              .background(Color.white)        // White background for visibility
              .cornerRadius(10)               // Rounded corners for modern look
              .shadow(radius: 5)              // Drop shadow for floating effect
              .padding(.horizontal)           // Horizontal padding from screen edges
              .onChange(of: searchText) { oldValue, newValue in
                  // Trigger search whenever the user types
                  performSearch(query: newValue)
              }
          
          // Display suggestions list only when we have results
          if !suggestions.isEmpty {
              List(suggestions, id: \.mapboxId) { suggestion in
                  // Each suggestion displayed as a card with name and description
                  VStack(alignment: .leading, spacing: 4) {
                      // Primary name of the location (e.g., "Central Park")
                      Text(suggestion.name)
                          .font(.headline)
                          .fontWeight(.bold)
                      
                      // Secondary description (e.g., "New York, NY, United States")
                      Text(suggestion.description ?? "")
                          .font(.caption)
                          .foregroundColor(.gray)
                  }
                  .padding(.vertical, 4)
                  .onTapGesture {
                      // Handle user selection of this suggestion
                      handleSuggestionSelection(suggestion)
                  }
              }
              .frame(minHeight: 0, maxHeight: 500)    // Limit height to avoid taking full screen
              .listStyle(PlainListStyle())            // Remove default list styling
              .background(Color.white)                // White background for visibility
              .cornerRadius(10)                       // Rounded corners to match search field
              .shadow(radius: 5)                      // Drop shadow for floating effect
              .padding(.horizontal)                   // Match search field padding
          }
      }
  }
  
  
  // Performs a search query using Mapbox Search API
  private func performSearch(query: String) {
      // Clear suggestions if search text is empty
      guard !query.isEmpty else {
          suggestions = []
          return
      }
      
      // Call Mapbox PlaceAutocomplete API with search parameters
      placeAutocomplete.suggestions(
          for: query
      ) { result in
          // Handle the API response
          switch result {
          case .success(let suggestionResults):
              // Update UI on main thread when we receive successful results
              DispatchQueue.main.async {
                  print("Received (suggestionResults.count) suggestions:")
                  // Debug: Print each suggestion for development purposes
                  for (index, suggestion) in suggestionResults.enumerated() {
                      print("Suggestion (index): (suggestion)")
                  }
                  // Update the suggestions array to trigger UI refresh
                  suggestions = suggestionResults
              }
              
          case .failure(let error):
              // Handle API errors (network issues, invalid API key, etc.)
              debugPrint(error)
              DispatchQueue.main.async {
                  // Clear suggestions on error
                  suggestions = []
              }
          }
      }
  }
  
  
  // Handles when user taps on a search suggestion
  private func handleSuggestionSelection(_ selectedSuggestion: PlaceAutocomplete.Suggestion) {
      // Clean up the UI by clearing suggestions and search text
      suggestions = []
      searchText = ""
      
      // Convert the suggestion into a full result with complete location details
      placeAutocomplete.select(suggestion: selectedSuggestion) { result in
          switch result {
          case .success(let suggestionResult):
              // Successfully retrieved full location details
              // Pass the result back to the parent view (ContentView)
              onSuggestionSelected(suggestionResult)
              
          case .failure(let error):
              // Handle selection errors (network issues, invalid suggestion, etc.)
              debugPrint(error)
          }
      }
  }
}

```

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-search-location-fly-to-marker-9d664090afa72b8ac85894cc0782d748.mp4).

## Enhance search with proximity and bounding box

Now that you have basic search functionality in place, you can enhance the search experience by providing more relevant results based on the user's location or a specific geographic area.

### Bias results around a point with `proximity`

To bias search results toward a specific location, you can use the `proximity` parameter in your `search Options` object. This parameter takes a `Point` representing the desired location and will bias search results to favor locations near this point.

In the `SearchScreen.swift` file, locate the `placeAutocomplete.suggestions` configuration in the search engine call. You can update it to include a `proximity` parameter. In the code below we've added a proximity to the Distillery District in Toronto, Canada.

```
placeAutocomplete.suggestions(
    for: query,

    // Optional: Bias results toward a specific location (Toronto city center)
    proximity: CLLocationCoordinate2D(latitude: 43.65050, longitude: -79.35954)
)
```

Restart your simulator, and now when you search `Coffee Shops` in the app, the results will be biased towards the Distillery District in Toronto, Canada.

### Restrict results to a geographic area with `boundingBox`

Sometimes it's desirable to restrict search results to a specific geographic area. For instance, in a local search app for a small business association. While `proximity` parameter biases results toward a specific location, the `boundingBox` parameter restricts results to a geographic area.

Add the `boundingBox` parameter to your `SearchOptions` in the `searchEngine.search()` call. This parameter takes a `BoundingBox` defined by two points: the southwest and northeast corners of the bounding box.

```swift

// Import necessary frameworks
import SwiftUI        // For the user interface components
import MapboxSearch   // For location search and autocomplete functionality

struct SearchScreen: View {
  // State to track the current search input text
  @State private var searchText: String = ""
  
  // Array to store search suggestions returned from Mapbox Search API
  @State private var suggestions: [PlaceAutocomplete.Suggestion] = []
  
  // Mapbox PlaceAutocomplete instance for performing searches
  @State private var placeAutocomplete = PlaceAutocomplete()
  
  // Callback function to notify parent view when a suggestion is selected
  let onSuggestionSelected: (PlaceAutocomplete.Result) -> Void
  
  
  var body: some View {
      VStack(spacing: 20) {
          // Search input field with floating card appearance
          TextField("Search for places...", text: $searchText)
              .textFieldStyle(RoundedBorderTextFieldStyle())  // Built-in rounded border style
              .background(Color.white)        // White background for visibility
              .cornerRadius(10)               // Rounded corners for modern look
              .shadow(radius: 5)              // Drop shadow for floating effect
              .padding(.horizontal)           // Horizontal padding from screen edges
              .onChange(of: searchText) { oldValue, newValue in
                  // Trigger search whenever the user types
                  performSearch(query: newValue)
              }
          
          // Display suggestions list only when we have results
          if !suggestions.isEmpty {
              List(suggestions, id: \.mapboxId) { suggestion in
                  // Each suggestion displayed as a card with name and description
                  VStack(alignment: .leading, spacing: 4) {
                      // Primary name of the location (e.g., "Central Park")
                      Text(suggestion.name)
                          .font(.headline)
                          .fontWeight(.bold)
                      
                      // Secondary description (e.g., "New York, NY, United States")
                      Text(suggestion.description ?? "")
                          .font(.caption)
                          .foregroundColor(.gray)
                  }
                  .padding(.vertical, 4)
                  .onTapGesture {
                      // Handle user selection of this suggestion
                      handleSuggestionSelection(suggestion)
                  }
              }
              .frame(minHeight: 0, maxHeight: 500)    // Limit height to avoid taking full screen
              .listStyle(PlainListStyle())            // Remove default list styling
              .background(Color.white)                // White background for visibility
              .cornerRadius(10)                       // Rounded corners to match search field
              .shadow(radius: 5)                      // Drop shadow for floating effect
              .padding(.horizontal)                   // Match search field padding
          }
      }
  }
  
  
  // Performs a search query using Mapbox Search API
  private func performSearch(query: String) {
      // Clear suggestions if search text is empty
      guard !query.isEmpty else {
          suggestions = []
          return
      }
      
      // Call Mapbox PlaceAutocomplete API with search parameters
      placeAutocomplete.suggestions(
          for: query,
          // Optional: Limit search results to a specific geographic region
          // This example uses a bounding box around Toronto, Canada
          region: BoundingBox(
              CLLocationCoordinate2D(
                  latitude:  43.60698,   // Southwest corner latitude
                  longitude: -79.49555   // Southwest corner longitude
              ),
              CLLocationCoordinate2D(
                  latitude: 43.75953,    // Northeast corner latitude
                  longitude: -79.29422   // Northeast corner longitude
              )
          ),
          // Optional: Bias results toward a specific location (Toronto city center)
          proximity: CLLocationCoordinate2D(latitude: 43.65050, longitude: -79.35954)
          
      ) { result in
          // Handle the API response
          switch result {
          case .success(let suggestionResults):
              // Update UI on main thread when we receive successful results
              DispatchQueue.main.async {
                  print("Received (suggestionResults.count) suggestions:")
                  // Debug: Print each suggestion for development purposes
                  for (index, suggestion) in suggestionResults.enumerated() {
                      print("Suggestion (index): (suggestion)")
                  }
                  // Update the suggestions array to trigger UI refresh
                  suggestions = suggestionResults
              }
              
          case .failure(let error):
              // Handle API errors (network issues, invalid API key, etc.)
              debugPrint(error)
              DispatchQueue.main.async {
                  // Clear suggestions on error
                  suggestions = []
              }
          }
      }
  }
  
  
  // Handles when user taps on a search suggestion
  private func handleSuggestionSelection(_ selectedSuggestion: PlaceAutocomplete.Suggestion) {
      // Clean up the UI by clearing suggestions and search text
      suggestions = []
      searchText = ""
      
      // Convert the suggestion into a full result with complete location details
      placeAutocomplete.select(suggestion: selectedSuggestion) { result in
          switch result {
          case .success(let suggestionResult):
              // Successfully retrieved full location details
              // Pass the result back to the parent view (ContentView)
              onSuggestionSelected(suggestionResult)
              
          case .failure(let error):
              // Handle selection errors (network issues, invalid suggestion, etc.)
              debugPrint(error)
          }
      }
  }
}

```

These coordinates were acquired using Mapbox's [Location Helper](https://labs.mapbox.com/location-helper/) tool, which allows you to find coordinates for any location. The bounding box can be visualized as a rectangle on the map, and search results will only include locations within this rectangle. You can learn more about [Bounding Boxes](https://docs.mapbox.com/help/glossary/bounding-box/) in the glossary.

![Screenshot of bounding box on the map.](https://docs.mapbox.com/help/assets/ideal-img/tutorials--android-location-search--bounding-box.2a03e56.480.png)

## Final Product

🎉 **Congratulations!** You have successfully built a location search feature for your iOS app using the **Mapbox Search SDK** and **Maps SDK for iOS**.

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/help/help/assets/medias/tutorials--ios-search-location-full-demo-be2705965c276fd686baa5f47aade8e2.mp4).

Below is the final code from the `ContentView.swift` and `SearchScreen.swift` files. You can compare your code to the final code to make sure everything is working correctly.

**ContentView.swift**

```swift
// Import necessary frameworks for the app
import SwiftUI          // For the user interface
import MapboxMaps       // For map rendering and interaction
import MapboxSearch     // For location search functionality
import CoreLocation     // For coordinate handling

struct ContentView: View {
    // Viewport controls the map's camera position (center, zoom level)
    // Starting with a view of the continental United States
    @State var viewport: Viewport = .camera(
        center: CLLocationCoordinate2D(
            latitude: 39.5,   
            longitude: -98.0  
        ),
        zoom: 2)
    
    // Store the selected search result to display as a marker
    @State private var selectedResult: PlaceAutocomplete.Result?
    
    // Track the ID of the selected result to trigger map animations
    @State private var selectedResultId: String?
    
    
    var body: some View {
        ZStack {
            // Map takes full screen as the base layer
            MapReader { proxy in
                Map(viewport: $viewport) {
                    // Conditionally add a marker annotation when a location is selected
                    if let result = selectedResult, let coordinate = result.coordinate {
                        PointAnnotation(coordinate: CLLocationCoordinate2D(
                            latitude: coordinate.latitude,
                            longitude: coordinate.longitude
                        ))
                        .image(named: "map_marker")      // Use custom marker image
                        .iconSize(0.5)               // Scale the marker to half size
                        .iconAnchor(.bottom)         // Anchor point at bottom of marker
                        .iconOffset(x: 0, y: 12)    // Fine-tune marker positioning
                    }
                }
                .mapStyle(.standard())  // Use the Mapbox Standard style
            }
            
            // Floating search interface overlaid on the map
            VStack {
                // Search component handles autocomplete and result selection
                SearchScreen(onSuggestionSelected: handleSuggestionSelection)
                Spacer()  // Push search to top of screen
            }
            .padding(.top, 50)  // Add padding to avoid status bar overlap
        }
        // Monitor changes to selectedResultId to trigger map animations
        .onChange(of: selectedResultId) { oldValue, newValue in
            // When a new result is selected, animate the map to that location
            if let _ = newValue, let result = selectedResult {
                flyToLocation(result)
            }
        }
    }
    
    // Called when user selects a search suggestion
    private func handleSuggestionSelection(_ result: PlaceAutocomplete.Result) {
        selectedResult = result           // Store the selected result
        selectedResultId = result.mapboxId // Update ID to trigger map animation
    }
    
    // Animates the map camera to fly to the selected location
    private func flyToLocation(_ result: PlaceAutocomplete.Result) {
        // Ensure the result has valid coordinates
        if let coordinate = result.coordinate {
            // Convert search result coordinate to CLLocationCoordinate2D
            let mapCoordinate = CLLocationCoordinate2D(
                latitude: coordinate.latitude,
                longitude: coordinate.longitude
            )
            
            // Animate the viewport change with a smooth fly animation
            withViewportAnimation(.fly(duration: 3)) {
                viewport = .camera(
                    center: mapCoordinate,
                    zoom: 15.0  // Zoom in for detailed view of the location
                )
            }
        }
    }
}



```

**SearchScreen.swift**

```swift
// Import necessary frameworks
import SwiftUI        // For the user interface components
import MapboxSearch   // For location search and autocomplete functionality

struct SearchScreen: View {
    // State to track the current search input text
    @State private var searchText: String = ""
    
    // Array to store search suggestions returned from Mapbox Search API
    @State private var suggestions: [PlaceAutocomplete.Suggestion] = []
    
    // Mapbox PlaceAutocomplete instance for performing searches
    @State private var placeAutocomplete = PlaceAutocomplete()
    
    // Callback function to notify parent view when a suggestion is selected
    let onSuggestionSelected: (PlaceAutocomplete.Result) -> Void
    
    
    var body: some View {
        VStack(spacing: 20) {
            // Search input field with floating card appearance
            TextField("Search for places...", text: $searchText)
                .textFieldStyle(RoundedBorderTextFieldStyle())  // Built-in rounded border style
                .background(Color.white)        // White background for visibility
                .cornerRadius(10)               // Rounded corners for modern look
                .shadow(radius: 5)              // Drop shadow for floating effect
                .padding(.horizontal)           // Horizontal padding from screen edges
                .onChange(of: searchText) { oldValue, newValue in
                    // Trigger search whenever the user types
                    performSearch(query: newValue)
                }
            
            // Display suggestions list only when we have results
            if !suggestions.isEmpty {
                List(suggestions, id: \.mapboxId) { suggestion in
                    // Each suggestion displayed as a card with name and description
                    VStack(alignment: .leading, spacing: 4) {
                        // Primary name of the location (e.g., "Central Park")
                        Text(suggestion.name)
                            .font(.headline)
                            .fontWeight(.bold)
                        
                        // Secondary description (e.g., "New York, NY, United States")
                        Text(suggestion.description ?? "")
                            .font(.caption)
                            .foregroundColor(.gray)
                    }
                    .padding(.vertical, 4)
                    .onTapGesture {
                        // Handle user selection of this suggestion
                        handleSuggestionSelection(suggestion)
                    }
                }
                .frame(minHeight: 0, maxHeight: 500)    // Limit height to avoid taking full screen
                .listStyle(PlainListStyle())            // Remove default list styling
                .background(Color.white)                // White background for visibility
                .cornerRadius(10)                       // Rounded corners to match search field
                .shadow(radius: 5)                      // Drop shadow for floating effect
                .padding(.horizontal)                   // Match search field padding
            }
        }
    }
    
    
    // Performs a search query using Mapbox Search API
    private func performSearch(query: String) {
        // Clear suggestions if search text is empty
        guard !query.isEmpty else {
            suggestions = []
            return
        }
        
        // Call Mapbox PlaceAutocomplete API with search parameters
        placeAutocomplete.suggestions(
            for: query,
            // Optional: Limit search results to a specific geographic region
            // This example uses a bounding box around Toronto, Canada
            region: BoundingBox(
                CLLocationCoordinate2D(
                    latitude:  43.60698,   // Southwest corner latitude
                    longitude: -79.49555   // Southwest corner longitude
                ),
                CLLocationCoordinate2D(
                    latitude: 43.75953,    // Northeast corner latitude
                    longitude: -79.29422   // Northeast corner longitude
                )
            ),
            // Optional: Bias results toward a specific location (Toronto city center)
            proximity: CLLocationCoordinate2D(latitude: 43.65050, longitude: -79.35954)
            
        ) { result in
            // Handle the API response
            switch result {
            case .success(let suggestionResults):
                // Update UI on main thread when we receive successful results
                DispatchQueue.main.async {
                    print("Received \(suggestionResults.count) suggestions:")
                    // Debug: Print each suggestion for development purposes
                    for (index, suggestion) in suggestionResults.enumerated() {
                        print("Suggestion \(index): \(suggestion)")
                    }
                    // Update the suggestions array to trigger UI refresh
                    suggestions = suggestionResults
                }
                
            case .failure(let error):
                // Handle API errors (network issues, invalid API key, etc.)
                debugPrint(error)
                DispatchQueue.main.async {
                    // Clear suggestions on error
                    suggestions = []
                }
            }
        }
    }
    
    
    // Handles when user taps on a search suggestion
    private func handleSuggestionSelection(_ selectedSuggestion: PlaceAutocomplete.Suggestion) {
        // Clean up the UI by clearing suggestions and search text
        suggestions = []
        searchText = ""
        
        // Convert the suggestion into a full result with complete location details
        placeAutocomplete.select(suggestion: selectedSuggestion) { result in
            switch result {
            case .success(let suggestionResult):
                // Successfully retrieved full location details
                // Pass the result back to the parent view (ContentView)
                onSuggestionSelected(suggestionResult)
                
            case .failure(let error):
                // Handle selection errors (network issues, invalid suggestion, etc.)
                debugPrint(error)
            }
        }
    }
}
```

> **Related content (related): [Download the final code](https://github.com/mapbox/tutorials/)**
> 
> The full source code for this tutorial is available on GitHub. You can download the final code and run it in Xcode to see the complete location search functionality in action.

## Next steps

Now that you've successfully integrated location search into an iOS app, here is a review of what you accomplished and some ideas for further enhancements:

### What we covered

-   Installing the **Search SDK for iOS** and setting up your secret token
-   Adding location permissions and marker assets to your project
-   Creating a main activity with Maps SDK integration
-   Building a search input component using SwiftUI
-   Implementing autocomplete suggestions to display search results
-   Handling suggestion selection and map interactions
-   Adding markers to the map for selected locations
-   Enhancing search with proximity and bounding box parameters

### Further customization

With the basic location search functionality in place, you can enhance your app by:

#### Customize search interactivity to use search categories (restaurants, gas stations, etc.).

If you inspect the code inside the `SearchScreen.swift` file, you'll notice this line of code inside the `Surface` component that renders the suggestions list:

```swift
 // Filter out category suggestion types
 val filteredSuggestions = suggestions.filter { it.type !is Category }
```

This filters out suggestions that are of type `Category`, which are returned by the Search Box API. A search suggestion of type `Category` is a category of places, such as "Restaurants" or "Gas Stations" and is an array of `Results` rather than a single result that we've used in this tutorial. You can remove this filter to include category suggestions in your search results. Then in your `handleSuggestionSelection` function, you can handle the multiple results returned by the category suggestion in the `onResults` callback.

From there you could add a marker and a [`ViewAnnotation`](https://docs.mapbox.com/ios/maps/api/latest/documentation/mapboxmaps/viewannotation/) to display the name of each result next to the marker and move the map to re-center to map to view all the results.

#### Additional features to consider:

-   **Implementing search history**: Store and display recent searches
-   **Custom marker styling**: Create branded markers that match your app's design
-   **Offline search**: Implement offline search capabilities for better user experience

### Learn more

> **Related content (related): [Search SDK for iOS documentation](https://docs.mapbox.com/iOS/search/guides/)**
> 
> Explore advanced features and customization options for the Mapbox Search SDK.

> **Related content (related): [Maps SDK for iOS documentation](https://docs.mapbox.com/iOS/maps/guides/)**
> 
> Learn more about map customization, styling, and advanced features.