WWDC Quick Look 💓 By SwiftGGTeam
Unlock the power of places with MapKit

Unlock the power of places with MapKit

元の動画を見る

ハイライト

MapKit の今年のアップデートは「場所」という核心概念を軸に展開され、3 つのキーワードは参照、表示、検索です。


主要内容

これまで app 内で場所データを扱う際、開発者が保存できたのは緯度経度だけでした。問題は座標が古くなることです——Apple Maps の編集により場所の位置がいつでも修正される可能性があり、app 内の古い座標は誤りになります。営業時間や電話番号などの情報も継続的に変わるため、ハードコードでは追いつけません。

今年 MapKit は Place ID 体系を導入してこの問題を解決します。Apple Maps 内の各場所(レストラン、博物館、公園、学校など)には一意で永続的な識別子があります。Place ID を主キーとしてデータベースに保存し、次回 ID でクエリすると Apple が最新メンテナンスしているデータ(更新された位置、Web サイト、営業時間を含む)が取得できます。識別子はネイティブと Web で共通、永続化でき、クロスプラットフォームで共有できます(01:01)。

表示面では、MapKit が Apple Maps の Place Card 機能をサードパーティに開放しました。SelectionAccessory API により、ユーザーが地図の注釈をタップするとシステム Maps と同じスタイルの場所詳細カードが表示され、全画面、コンパクト、Maps で開くの 3 つの表示モードをサポートします(06:30)。ページに地図ビューがなくても、MapItemDetail と PlaceDetail で場所情報を独立してレンダリングでき、UIKit / AppKit / SwiftUI それぞれに対応 API があります。Web 側では Maps Embed API が新たに追加され、JavaScript を書かずに Place Card 付きの地図を埋め込めます。

検索 API も同時に強化されました。AddressFilter で都市や郵便番号などの住所タイプのみに検索を限定でき、RegionPriority で結果を指定エリア内に強制できます。サーバー API にはページネーションも追加されました(12:12)。

詳細

Place ID:座標の代わりに識別子を使う

Place ID を取得する方法は 2 つあります。既存の Search / Geocoding API を使うか、Apple が新たに提供する Place ID Lookup ツールで一括検索します。Lookup ツールは developer.apple.com/maps の Resources ページにあり、検索と地図クリックの 2 つの操作をサポートします(02:30)。

ID を取得したら、Swift 側で MKMapItemRequest 経由で MKMapItem を非同期取得します(03:06):

struct PlaceMapView: View {
    var placeID: String // "I63802885C8189B2B"

    @State private var item: MKMapItem?

    var body: some View {
        Map {
            if let item {
                Marker(item: item)
            }
        }
        .task {
            guard let identifier = MKMapItem.Identifier(
                rawValue: placeID
            ) else {
                return
            }
            let request = MKMapItemRequest(
                mapItemIdentifier: identifier
            )
            item = try? await request.mapItem
        }
    }
}

キーポイント:

  • MKMapItem.Identifier(rawValue:) で Place ID 文字列を型安全な識別子に変換
  • MKMapItemRequest で非同期リクエストを開始し、Apple サーバーから最新の場所データを取得
  • Marker(item:) で直接レンダリングし、座標を手動設定する必要なし——位置は Apple データが駆動
  • Apple Maps の編集で場所の位置が修正されても、Place ID を使う app はコード変更なしで変更を自動継承

Web 側の対応 API は mapkit.PlaceLookup03:44)で、同じ Place ID で Place オブジェクトを取得後、PlaceAnnotation で注釈をレンダリングします。Token も今年簡素化され、新しい token provisioning ツールはドメイン紐付けの静的文字列を生成し、動的 JWT 生成が不要になり、手動で失効するまで期限切れになりません(04:46)。

SelectionAccessory:地図上に Place Card を表示

地図上の注釈に Place Card を追加するには、SwiftUI では modifier 1 つで済みます(07:32):

struct VisitedStoresView: View {
    var visitedStores: [MKMapItem]
    @State private var selection: MKMapItem?

    var body: some View {
        Map(selection: $selection) {
            ForEach(visitedStores, id: \.self) { store in
                Marker(item: store)
            }
            .mapItemDetailSelectionAccessory()
        }
    }
}

キーポイント:

  • mapItemDetailSelectionAccessory() はデフォルトで automatic モードを使用し、MapKit がプラットフォームと地図サイズに応じて全画面またはコンパクト表示を選択
  • .callout を渡すと吹き出しスタイルを強制

より実用的なシナリオ:地図上のすべての POI で Place Card を有効にし、自分の注釈だけでなく任意の POI に対応(11:17):

struct VisitedStoresView: View {
    var visitedStores: [MKMapItem]
    @State private var selection: MapSelection<MKMapItem>?

    var body: some View {
        Map(selection: $selection) {
            ForEach(visitedStores, id: \.self) { store in
                Marker(item: store)
                    .tag(MapSelection(store))
            }
            .mapItemDetailSelectionAccessory(.callout)
        }
        .mapFeatureSelectionAccessory(.callout)
    }
}

キーポイント:

  • MapSelection<MKMapItem> は統一された選択タイプで、カスタム注釈と地図上の POI の両方をサポート
  • .mapFeatureSelectionAccessory(.callout) により、ユーザーが地図上の任意の POI をタップして Place Card を表示可能
  • ユーザーが注釈周辺の公園やレストランなどを探索する際、直接コンテキスト情報を取得

地図ビューがないシナリオでは、mapItemDetailSheet(item:) で List 内に Place Card を表示(09:15):

struct StoreList: View {
    var stores: [MKMapItem]
    @State private var selectedStore: MKMapItem?

    var body: some View {
        List(
            stores,
            id: \.self,
            selection: $selectedStore
        ) {
            Text($0.name ?? "Apple Store")
        }
        .mapItemDetailSheet(item: $selectedStore)
    }
}

キーポイント:

  • mapItemDetailSheet は地図コンテキスト外で Place Card を表示し、UIKit / AppKit / SwiftUI をサポート
  • Web 側の同等 API は mapkit.PlaceDetail で、mapkit.PlaceSelectionAccessory と併用

検索の強化:AddressFilter と RegionPriority

「Cupertino」を検索したいが同名の店舗にマッチしたくない場合、AddressFilter で都市のみに限定(14:41):

struct CoffeeMap: View {
    @State private var position: MapCameraPosition = .automatic
    @State private var coffeeShops: [MKMapItem] = []

    var body: some View {
        Map(position: $position) {
            ForEach(coffeeShops, id: \.self) { café in
                Marker(item: cafe)
            }
        }
        .task {
            guard let cupertino = await findCity() else {
                return
            }
            coffeeShops = await findCoffee(in: cupertino)
        }
    }

    private func findCity() async -> MKMapItem? {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = "cupertino"

        request.addressFilter = MKAddressFilter(
            including: .locality
        )

        let search = MKLocalSearch(request: request)
        let response = try? await search.start()
        return response?.mapItems.first
    }

    private func findCoffee(in city: MKMapItem ) async -> [MKMapItem] {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = "coffee"
        let downtown = MKCoordinateRegion(
            center: city.placemark.coordinate,
            span: .init(
                latitudeDelta: 0.01,
                longitudeDelta: 0.01
            )
        )
        request.region = downtown
        request.regionPriority = .required
        let search = MKLocalSearch(request: request)
        let response = try? await search.start()
        return response?.mapItems ?? []
    }
}

キーポイント:

  • MKAddressFilter(including: .locality) は都市レベルの結果のみを返し、同名店舗をフィルタ
  • request.regionPriority = .required で検索結果を指定エリア内に強制し、地図をズームしても遠方の注釈が表示されない
  • 検索は音楽会場、スケートパーク、城、川や山などの自然地形など、より多くの場所タイプをサポート(12:18
  • Web 側は mapkit.AddressFilter.including([mapkit.AddressCategory.Locality])mapkit.Search.RegionPriority.Required に対応

重要ポイント

  • やること:データベースの緯度経度フィールドを Place ID に置き換える。価値がある理由:座標は古くなるデータ——Apple Maps の編集で位置が修正されると、ハードコードされた座標は誤りになる。Place ID は安定した参照で、クエリ時に常に Apple が最新メンテナンスしている情報を取得できる。始め方:新規追加の場所は直接 Place ID を保存。既存データは Place ID Lookup ツールでバッチ移行。

  • やること:地図に .mapFeatureSelectionAccessory(.callout) を追加する。価値がある理由:ユーザーが地図上の任意の POI をタップして Place Card を見られ、周辺のコンテキスト(公園、レストランなど)を取得できる。探索型 app に非常に価値が高く、1 行のコードで済む。始め方:既存の Map ビューにこの modifier を追加するだけで、注釈ロジックの変更は不要。

  • やること:検索で AddressFilter + RegionPriority を組み合わせる。価値がある理由:「Cupertino」検索で同名店舗のノイズ結果を回避。地図をズームした後に遠方の注釈が視野を汚染するのを防ぐ。始め方:都市検索に addressFilter: .locality、エリア検索に regionPriority: .required を追加。各 1 行。

  • やること:Maps Embed API で Web ページに場所情報を表示する。価値がある理由:JavaScript 不要——Apple Developer サイトの Create a Map ツールで Place ID と token を設定し、生成された HTML スニペットをコピーするだけ。単一場所の表示シナリオに最適。始め方:developer.apple.com/maps → Resources → Create a Map で Web Embed モードを選択。

関連セッション

コメント

GitHub Issues · utterances