Posts iOS MapKitで地点情報のサジェスト表示

MapKitで地点情報のサジェスト表示

はじめに

MapKitは、appleより提供されている地図や衛星写真を取り扱うためのフレームワークです。 UI上に地図を表示する以外にも様々なことが可能です。 今回は、入力中に候補となる地点情報を提案する「サジェスト機能」としての使い方を紹介します。

環境

  • OS: macOS Sonoma 14.7.1
  • Xcode: 16.2.0

サジェスト内容を取得する

構成

以下のような構成でアプリを作成することにしてみます。 なお、ContentViewContentViewModelSuggestModelに関しては今回の主題と外れるので、完成したコードのみ示し、主にSuggestRepositoryに付いて解説します。

ContentView

Viewの描画を行う。 入力欄と、サジェスト内容のリストを持つ

struct ContentView: View {
    @State var viewModel = ContentViewModel()

    var body: some View {
        VStack {
            TextField("検索", text: $viewModel.searchText)
                .onChange(of: viewModel.searchText) {
                    Task  { await viewModel.requestSuggets() }
                }

            List(viewModel.suggests, id: \.self) { suggest in
                VStack(alignment: .leading) {
                    Text("名前: \(suggest.name)")
                    Text("緯度: \(suggest.latitude)")
                    Text("軽度: \(suggest.longitude)")
                }
            }
        }
        .padding()
    }
}

ContentViewModel

ContentViewのViewModel。 サジェスト内容の一覧suggestsや検索窓の入力内容searchTextを持つ

@MainActor
@Observable
final class ContentViewModel {
    var suggests = [SuggestModel]()
    var searchText: String = ""

    /// SuggestRepositoryからサジェスト内容の一覧を受けとり、`suggests`を更新する
    func requestSuggets() async {
        let suggests = await SuggestRepository.shared.requestSuggests(query: searchText)
        self.suggests = suggests
    }
}

SuggestModel

サジェスト内容を保持する。 SuggestModelの配列をSugegstRepositoryから返すことでContentViewModelにサジェスト情報を伝える。 今回はサンプルとして場所の名前と緯度経度を返すことにする。

struct SuggestModel: Hashable {
    var name: String
    var latitude: Double
    var longitude: Double
}

SuggestRepositoryの実装

実装自体はシンプルなので、まずコードを示します。

import MapKit

actor SuggestRepository {
    static let shared = SuggestRepository()

    private init() {}

    func requestSuggests(query: String) async -> [SuggestModel] {
        let localSearch = MKLocalSearch.Request()
        localSearch.naturalLanguageQuery = query

        let response = try? await MKLocalSearch(request: localSearch).start()

        guard let mapItems = response?.mapItems else { return [] }
        let suggests = mapItems.compactMap {
            SuggestModel(
                name: $0.name ?? "",
                latitude: $0.placemark.coordinate.latitude,
                longitude: $0.placemark.coordinate.longitude
            )
        }
        return  suggests
    }
}

今回はシングルトンなactorで実装してみました。 requestSuggests(query:)は、検索対象の文字列を受取り、サジェスト結果を配列で返す関数です。 実装手順としては3stepです。

step1: リクエスト内容を設定

let localSearch = MKLocalSearch.Request()
localSearch.naturalLanguageQuery = query

まずは、上記の部分です。 MKLocalSearch.Requestという型がMapKitから提供されています。 このクラスのプロパティに検索内容を設定することで様々な条件の地点情報を取得できます。

  • naturalLanguageQuery: 文字列でサジェスト内容を絞り込めます
  • addressFilter: 検索結果に含める、または除外する位置情報を設定できます
  • pointOfInterestFilter: 検索結果煮含める、または除外するカテゴリを設定できます
  • etc

step2: リクエストを実行

let response = try? await MKLocalSearch(request: localSearch).start()

1行でサジェスト内容を取得できます。 今回はasync/awaitに対応したメゾットを利用しましたが、completion handlerのメゾットも用意されています。

step3: 整形

guard let mapItems = response?.mapItems else { return [] }
let suggests = mapItems.compactMap {
    SuggestModel(
        name: $0.name ?? "",
        latitude: $0.placemark.coordinate.latitude,
        longitude: $0.placemark.coordinate.longitude
    )
}
return  suggests

リクエストの実行結果はMKLocalSearch.Response型です。 プロパティとしてmapItemsが存在し、ここを参照することでMKMapItemの配列を得ることができます。 MKMapItemは、サジェストされた位置情報の名前や緯度経度、その他様々な情報が入っています。 必要な情報を取り出し、整形してreturnしましょう! あとはSuggestsModelをViewに反映すれば、位置情報サジェストを行いことができます!

まとめ

MapKitは地図の描画以外にも地点情報の取得などを簡単に実装できる、非常に便利なフレームワークです。 外部APIと比べると精度や網羅性で劣る部分もありますが、外部サービスに依存せず無料で利用できることが最大の利点かと思います。 是非利用してみてください!