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

# Place Autocomplete with custom UI elements

Your browser doesn't support HTML5 video. Open [link to the video](https://docs.mapbox.com/ios/ja/ios/ja/assets/medias/search-examples-autocomplete-548399462717d1db5fef7052333a9081.mp4).

This example shows how to add autocomplete search with a custom UI using the **Mapbox Search SDK for iOS**, providing an interactive search and result display experience. The `PlaceAutocompleteMainViewController` manages user input through a search bar, fetching location suggestions in real-time, and displaying them in a table view. It leverages the [`PlaceAutocomplete`](https://docs.mapbox.com/ios/search/api/core/2.29.0/Classes/PlaceAutocomplete.html) class to fetch suggestions based on user input, proximity, and filters, while also managing location access permissions. When a suggestion is selected, the result is passed to `PlaceAutocompleteResultViewController` for detailed display.

The `PlaceAutocompleteResultViewController` visualizes the selected result by displaying its details in a table view and adding corresponding annotations to a Mapbox map. It provides a smooth camera transition to the suggestion's coordinates and organizes key components such as name, type, distance, and categories into a user-friendly format. Together, these view controllers showcase how to integrate Place Autocomplete functionality into a user interface, combining search, map visualization, and structured result presentation.

> **Note: iOS Demo App Available**
> 
> This example code is part of the **Search SDK for iOS Demo App**, a working iOS project [available on Github](https://github.com/mapbox/mapbox-search-ios/tree/main/Demo). iOS developers are encouraged to run the demo app locally to interact with this example in a simulator and explore other features of the Search SDK.
> 
> The code below may depend on additional classes that are not part of the Search SDK itself, but are part of the demo app. You can find the full source code for the demo app in the [Mapbox Search iOS repository](https://github.com/mapbox/mapbox-search-ios/tree/main/)

**Swift**

Title: `PlaceAutocompleteMainViewController.swift`

[View on GitHub](https://github.com/mapbox/mapbox-search-ios/blob/main/Demo/PlaceAutocomplete/PlaceAutocompleteMainViewController.swift)

```swift
import MapboxSearch
import UIKit

final class PlaceAutocompleteMainViewController: UIViewController {
    @IBOutlet private var tableView: UITableView!
    @IBOutlet private var messageLabel: UILabel!

    private lazy var placeAutocomplete = PlaceAutocomplete()

    private var cachedSuggestions: [PlaceAutocomplete.Suggestion] = []

    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
        configureUI()
    }
}

// MARK: - UISearchResultsUpdating

extension PlaceAutocompleteMainViewController: UISearchResultsUpdating {
    func updateSearchResults(for searchController: UISearchController) {
        guard let text = searchController.searchBar.text,
              !text.isEmpty
        else {
            cachedSuggestions = []

            reloadData()
            return
        }

        placeAutocomplete.suggestions(
            for: text,
            proximity: locationManager.location?.coordinate,
            filterBy: .init(types: [.POI], navigationProfile: .driving)
        ) { [weak self] result in
            guard let self else { return }

            switch result {
            case .success(let suggestions):
                cachedSuggestions = suggestions
                reloadData()

            case .failure(let error):
                print(error)
            }
        }
    }
}

// MARK: - UITableViewDataSource & UITableViewDelegate

extension PlaceAutocompleteMainViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        cachedSuggestions.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "suggestion-tableview-cell"

        let tableViewCell: UITableViewCell = if let cachedTableViewCell = tableView
            .dequeueReusableCell(withIdentifier: cellIdentifier)
        {
            cachedTableViewCell
        } else {
            UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
        }

        let suggestion = cachedSuggestions[indexPath.row]

        tableViewCell.textLabel?.text = suggestion.name
        tableViewCell.accessoryType = .disclosureIndicator

        var description = suggestion.description ?? ""
        if let distance = suggestion.distance {
            description += "\n\(PlaceAutocomplete.Result.distanceFormatter.string(fromDistance: distance))"
        }
        if let estimatedTime = suggestion.estimatedTime {
            description += "\n\(PlaceAutocomplete.Result.measurementFormatter.string(from: estimatedTime))"
        }

        tableViewCell.detailTextLabel?.text = description
        tableViewCell.detailTextLabel?.textColor = UIColor.darkGray
        tableViewCell.detailTextLabel?.numberOfLines = 3

        return tableViewCell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        placeAutocomplete.select(suggestion: cachedSuggestions[indexPath.row]) { [weak self] result in
            switch result {
            case .success(let suggestionResult):
                let resultVC = PlaceAutocompleteResultViewController.instantiate(with: suggestionResult)
                self?.navigationController?.pushViewController(resultVC, animated: true)

            case .failure(let error):
                print("Suggestion selection error \(error)")
            }
        }
    }
}

// MARK: - Private

extension PlaceAutocompleteMainViewController {
    private func reloadData() {
        messageLabel.isHidden = !cachedSuggestions.isEmpty
        tableView.isHidden = cachedSuggestions.isEmpty

        tableView.reloadData()
    }

    private func configureUI() {
        configureSearchController()
        configureTableView()
        configureMessageLabel()
    }

    private func configureSearchController() {
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchResultsUpdater = self
        searchController.obscuresBackgroundDuringPresentation = false
        searchController.searchBar.placeholder = "Search"
        searchController.searchBar.returnKeyType = .done

        navigationItem.searchController = searchController
    }

    private func configureMessageLabel() {
        messageLabel.text = "Start typing to get autocomplete suggestions"
    }

    private func configureTableView() {
        tableView.tableFooterView = UIView(frame: .zero)

        tableView.delegate = self
        tableView.dataSource = self

        tableView.isHidden = true
    }
}
```

**Swift**

Title: `PlaceAutocompleteDetailsViewController.swift`

[View on GitHub](https://github.com/mapbox/mapbox-search-ios/blob/main/Demo/PlaceAutocomplete/PlaceAutocompleteDetailsViewController.swift)

```swift
import MapboxMaps
import MapboxSearch
import MapKit
import UIKit

final class PlaceAutocompleteResultViewController: UIViewController {
    @IBOutlet private var tableView: UITableView!
    @IBOutlet private var mapView: MapView!
    lazy var annotationsManager = mapView.makeClusterPointAnnotationManager()

    private var result: PlaceAutocomplete.Result!
    private var resultComponents: [(name: String, value: String)] = []

    static func instantiate(with result: PlaceAutocomplete.Result) -> PlaceAutocompleteResultViewController {
        let storyboard = UIStoryboard(
            name: "Main",
            bundle: .main
        )

        let viewController = storyboard.instantiateViewController(
            withIdentifier: "PlaceAutocompleteResultViewController"
        ) as? PlaceAutocompleteResultViewController

        guard let viewController else {
            preconditionFailure()
        }

        viewController.result = result
        viewController.resultComponents = result.toComponents()

        return viewController
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        prepare()
    }

    func showAnnotations(results: [PlaceAutocomplete.Result], cameraShouldFollow: Bool = true) {
        annotationsManager.annotations = results.compactMap {
            PointAnnotation.pointAnnotation($0)
        }

        if cameraShouldFollow {
            cameraToAnnotations(annotationsManager.annotations)
        }
    }

    func cameraToAnnotations(_ annotations: [PointAnnotation]) {
        if annotations.count == 1, let annotation = annotations.first {
            mapView.camera.fly(
                to: .init(center: annotation.point.coordinates, zoom: 15),
                duration: 0.25,
                completion: nil
            )
        } else {
            do {
                let cameraState = mapView.mapboxMap.cameraState
                let coordinatesCamera = try mapView.mapboxMap.camera(
                    for: annotations.map(\.point.coordinates),
                    camera: CameraOptions(cameraState: cameraState),
                    coordinatesPadding: UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24),
                    maxZoom: nil,
                    offset: nil
                )

                mapView.camera.fly(to: coordinatesCamera, duration: 0.25, completion: nil)
            } catch {
                _Logger.searchSDK.error(error.localizedDescription)
            }
        }
    }
}

// MARK: - TableView data source

extension PlaceAutocompleteResultViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        result == nil ? .zero : resultComponents.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cellIdentifier = "result-cell"

        let tableViewCell: UITableViewCell = if let cachedTableViewCell = tableView
            .dequeueReusableCell(withIdentifier: cellIdentifier)
        {
            cachedTableViewCell
        } else {
            UITableViewCell(style: .value1, reuseIdentifier: cellIdentifier)
        }

        let component = resultComponents[indexPath.row]

        tableViewCell.textLabel?.text = component.name
        tableViewCell.detailTextLabel?.text = component.value
        tableViewCell.detailTextLabel?.textColor = UIColor.darkGray

        return tableViewCell
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        showSuggestionRegion()
    }
}

// MARK: - Private

extension PlaceAutocompleteResultViewController {
    /// Initial set-up
    private func prepare() {
        title = "Address"

        updateScreenData()
    }

    private func updateScreenData() {
        showAnnotations(results: [result])
        showSuggestionRegion()

        tableView.reloadData()
    }

    private func showSuggestionRegion() {
        guard let coordinate = result.coordinate else { return }

        let cameraOptions = CameraOptions(
            center: coordinate,
            zoom: 10.5
        )

        mapView.camera.ease(to: cameraOptions, duration: 0.4)
    }
}

extension PlaceAutocomplete.Result {
    static let measurementFormatter: MeasurementFormatter = {
        let formatter = MeasurementFormatter()
        formatter.unitOptions = [.naturalScale]
        formatter.numberFormatter.roundingMode = .halfUp
        formatter.numberFormatter.maximumFractionDigits = 0
        return formatter
    }()

    static let distanceFormatter: MKDistanceFormatter = {
        let formatter = MKDistanceFormatter()
        formatter.unitStyle = .abbreviated
        return formatter
    }()

    func toComponents() -> [(name: String, value: String)] {
        var components = [
            (name: "Name", value: name),
            (name: "Type", value: "\(type == .POI ? "POI" : "Address")"),
        ]

        if let address, let formattedAddress = address.formattedAddress(style: .short) {
            components.append(
                (name: "Address", value: formattedAddress)
            )
        }

        if let distance {
            components.append(
                (name: "Distance", value: PlaceAutocomplete.Result.distanceFormatter.string(fromDistance: distance))
            )
        }

        if let estimatedTime {
            components.append(
                (
                    name: "Estimated time",
                    value: PlaceAutocomplete.Result.measurementFormatter.string(from: estimatedTime)
                )
            )
        }

        if let phone {
            components.append(
                (name: "Phone", value: phone)
            )
        }

        if let reviewsCount = reviewCount {
            components.append(
                (name: "Reviews Count", value: "\(reviewsCount)")
            )
        }

        if let avgRating = averageRating {
            components.append(
                (name: "Rating", value: "\(avgRating)")
            )
        }

        if !categories.isEmpty {
            let categories = categories.count > 2 ? Array(categories.dropFirst(2)) : categories

            components.append(
                (name: "Categories", value: categories.joined(separator: ","))
            )
        }

        if let mapboxId {
            components.append(
                (name: "Mapbox ID", value: mapboxId)
            )
        }

        return components
    }
}
```