Amazon Web Services ブログ

Amazon Location Service を利用した Amplify Geo で iOS アプリに地図を追加

2022 年 4 月 5 日にリリースされた AWS Amplify Geo for iOS により、開発者はカスタマイズ可能なアノテーション付き地図や位置検索を iOS アプリケーションに迅速かつ容易に追加できるようになりました。位置情報 API は Amazon Location Service によって提供され、マップレンダリングは人気のあるオープンソースのマップライブラリである MapLibre から提供されます。本ブログでは、Amplify Geo の機能を紹介するために、シンプルなアプリケーションを構築します。

読むのに掛かる時間: 20分
手順を完了するのに掛かる時間:  60分

特徴

  • SwiftUI または UIKit を使用して、iOS アプリケーションに地図と検索機能を追加します。
  • Amazon Location Service の費用対効果とプライバシーの利点を活用します。
  • デバイス上でのレンダリングに、人気のあるオープンソースライブラリ、MapLibre GL Native を使用します。

何を作るか

今日は、ユーザーが次の旅行のためにホテルの近くのカフェを見つけることができる、シンプルで楽しい SwiftUI アプリケーションを構築します。Amplify Geo と Amplify MapLibre Adapter をセットアップして、地図を表示し、ユーザーが場所を検索し、その場所の近くのカフェを地図上に表示することができるようにします。加えて、Amplify Geo が提供する強力なカスタマイズ機能のいくつかを使用します。

前提条件

  • Xcode 12.0 以降
  • 最新の Amplify CLI バージョン(npm install -g @aws-amplify/cli と実行することで入手可能です。)

注意:現在、M1 macでのシミュレータ上でのレンダリングに関連する Xcode のバグがあります。そのため、M1 mac を使用している場合は、物理デバイス上でプロジェクトを実行する必要があります。

1. Amplify Geoのセットアップ

さっそく Xcode のプロジェクトを新規に作成しましょう。

iOS と App を選択し、next をクリックします。

次の画面で、プロダクト名に “beans “を入力します。インターフェースは SwiftUI、言語は Swift を選択します。

Xcode プロジェクトの設定ができたので、Amplify Geo を設定します。まず、ターミナルでプロジェクトのディレクトリに移動し、amplify init を実行します。表示されるプロンプトには、以下のように回答して下さい。

- Enter a name for the projects (beans)
--> hit enter

- Initialize the project with the above configuration? (Y/n)
--> n

- Enter a name for the environment (dev)
--> hit enter

- Choose your default editor:
--> Xcode (macOS only)

- Choose the type of app that you're building (use the arrow keys)
--> ios

- Select the authentication method you want to use
--> AWS profile

プロジェクトが正常に初期化され、クラウドに接続されたことを示すメッセージが表示されます! これで、Amplify プロジェクトの足場が整いました。

それでは、amplify add geo コマンドで Geo を追加する設定をしてみましょう。

- Select which capability you want to add: (use arrow keys)
--> Map (visualize the geospatial data)

- geo category resources require auth (Amazon Cognito). Do you want to add auth now? (Y/n)
--> Y

- Do you want to use the default authentication and security configuration? (use arrow keys)
--> Default configuration

- How do want users to be able to sign in? (use arrow keys)
--> Username

- Do you want to configure advanced settings? (use arrow keys)
--> No, I am done.

- Provide a name for the map:
--> Whatever you'd like. The default is fine.

- Who can access this Map?
--> Authorized and Guest users

- Do you want to configure advanced settings? (y/N)
--> N

これで、プロジェクトにマップが設定されました。Amplify CLI で最後に残っているのは、位置検索機能の追加です。これにはまた amplify add geo を使用します。

- Select which capability you want to add: (use arrow keys)
--> Location search (search by places, addresses, coordinates)

- Provide a name for the location search index (place index):
--> Whatever you'd like. The default is fine.

- Who can access this Map?
--> Authorized and Guest users

- Do you want to configure advanced settings? (y/N)
--> N

Amplify CLI の設定が完了したので、次に amplify push を実行します。

2. AmplifyMapLibreAdapter の追加

amplify push の実行中に、いよいよアプリケーションの作成を開始します。Xcode プロジェクトを開き、Project NavigatorPackage Dependencies に進みます。

アイコンをクリックし、右上の検索ボックスに https://github.com/aws-amplify/amplify-ios-maplibre を入力します。依存関係ルールとして Up to Next Major Version: 1.0.0 を選択し、Add Package をクリックします。

AmplifyMapLibreAdapterAmplifyMapLibreUI ライブラリを追加するため、両方のボックスにチェックを入れます。

3. Amplify の設定

次に、beansApp ファイルに移動して、Amplify の設定を行います。完了すると、ファイルは次のようになります。コンパイラは MapView がスコープ内で見つからないと言うので追加していきましょう。次のステップに進み、MapView を作成します。

import SwiftUI
import Amplify
import AWSCognitoAuthPlugin
import AWSLocationGeoPlugin

@main
struct beansApp: App {
    var body: some Scene {
        WindowGroup {
            MapView()
        }
    }
    
    init() {
        let auth = AWSCognitoAuthPlugin()
        let geo = AWSLocationGeoPlugin()
        do {
            try Amplify.add(plugin: auth)
            try Amplify.add(plugin: geo)
            try Amplify.configure()
        } catch {
            assertionFailure("Error configuring Amplify: \(error)")
        }
    }
}

4. アプリを作ろう

MapView という名前の SwiftUI View ファイルを作成します。MapView.swift に import文 import AmplifyMapLibreUI を追加し、Text("Hello, World!") プレースホルダーを AMLMapView に置き換えます。

import SwiftUI
import AmplifyMapLibreUI

struct MapView: View {
    var body: some View {
        AMLMapView()
    }
}

ビルドして実行 (cmd + r) すると、マップが出来上がりました!.edgesIgnoringSafeArea(.all) をつけると、マップが画面全体を覆うようになります。

AMLMapView()
  .edgesIgnoringSafeArea(.all)

地図単体でも十分ですが、アプリとして機能するものを作りたいと思います。AMLSearchBar を利用して、検索機能を追加してみましょう。MapView の本体を以下のように変更します。

var body: some View {
    ZStack(alignment: .top) {
        AMLMapView()
            .edgesIgnoringSafeArea(.all)
        
        ZStack(alignment: .center) {
            AMLSearchBar(
                text: .constant(""),
                displayState: .constant(.map),
                onEditing: {},
                onCommit: {},
                onCancel: {},
                showDisplayStateButton: false
            )
            .padding()
        }
    }
}

もう一度ビルドして実行します。これで、地図の上部に検索バーが表示されるようになりました。テキストを .constant("") で空文字列と定数バインドしていることにお気づきでしょうか。ユーザーが入力したテキストを検索バーが知らせてくれる必要があるので、これでは不十分です。

新しい Swift ファイルを作成し、それを MapViewModel と呼びます。MapViewModel の中に、これらの import 文と ObservableObject を追加します。

import SwiftUI
import AmplifyMapLibreUI
import AmplifyMapLibreAdapter
import Amplify
import CoreLocation

class MapViewModel: ObservableObject { 
  @Published var searchText = ""
}

ついでに、AMLMapViewState プロパティも追加しておきましょう。これは、AMLMapView の状態を管理するためのものです。AMLMapViewState が提供する様々な機能については、API ドキュメントを参照してください。

class MapViewModel: ObservableObject {
    @Published var searchText = ""
    @ObservedObject var mapState = AMLMapViewState()
}

MapViewに戻って AMLSearchBar に searchText を繋げる前に、先にこのメソッドを MapViewModel に追加しておきます。

func findHotel() {
    Amplify.Geo.search(for: searchText) { [weak self] result in
        switch result {
        case .success(let places):
            if let hotel = places.first { }
        case .failure(let error):
            print("Search failed with: \(error)")
            
        }
    }
}

findHotel() を追加した後、いくつかのコンパイラが表示されます。それらはすぐに対処されるので、心配しないで下さい。MapView.swift に戻って @StateObject viewModel プロパティを追加し、AMLMapViewmapState を追加し、searchText を繋ぎ、onCommit クロージャーに findHotel を渡します。完了すると、MapViewは以下のようになります。

struct MapView: View {
    @StateObject var viewModel = MapViewModel()
    
    var body: some View {
        ZStack(alignment: .top) {
            AMLMapView(mapState: viewModel.mapState)
                .edgesIgnoringSafeArea(.all)
            
            ZStack(alignment: .center) {
                AMLSearchBar(
                    text: $viewModel.searchText,
                    displayState: .constant(.map),
                    onEditing: {},
                    onCommit: viewModel.findHotel,
                    onCancel: {},
                    showDisplayStateButton: false
                )
                .padding()
            }
        }
    }
}

さて、ユーザーが検索バーに何かを入力すると、searchText プロパティはその変化を観察します。ユーザーが go をタップすると、Amazon Location Service にリクエストして、該当する結果を取得するようにします。

beans にはまだ何も表示されていませんが、もう少しで表示されます。MapViewModel.swift に次の2つのメソッドを追加することで、beansは以下のようになります。

  1. 検索結果から最も適切な結果を取得する。
  2. その結果を中心に地図を表示する。
  3. その結果の近くでコーヒーを検索する。
  4. 検索結果ごとに、地図上にマーカーを表示する。
private func setCenter(for place: Geo.Place) {
    DispatchQueue.main.async {
        self.mapState.center = CLLocationCoordinate2D(place.coordinates)
    }
}

private func findCoffee(near hotel: Geo.Place) {
    Amplify.Geo.search(for: "coffee", options: .init(area: .near(hotel.coordinates))) { [weak self] result in
        switch result {
        case .success(let cafes):
            DispatchQueue.main.async {
                self?.mapState.features = AmplifyMapLibre.createFeatures(cafes)
            }
        case .failure(let error):
            print(error)
        }
    }
}

(エラーハンドリングは読者への課題として残しておきます😀)

あとは、最初の検索結果でこれらのメソッドを呼び出すだけです。if let hotel = places.first ブロックに、以下を追加します。

class MapViewModel: ObservableObject {
    @Published var searchText = ""
    @ObservedObject var mapState = AMLMapViewState()
    
    private func setCenter(for place: Geo.Place) {
        DispatchQueue.main.async {
            self.mapState.center = CLLocationCoordinate2D(place.coordinates)
        }
    }

    private func findCoffee(near hotel: Geo.Place) {
        Amplify.Geo.search(for: "coffee", options: .init(area: .near(hotel.coordinates))) { [weak self] result in
            switch result {
            case .success(let cafes):
                DispatchQueue.main.async {
                    self?.mapState.features = AmplifyMapLibre.createFeatures(cafes)
                }
            case .failure(let error):
                print(error)
            }
        }
    }
    
    func findHotel() {
        Amplify.Geo.search(for: searchText) { [weak self] result in
            switch result {
            case .success(let places):
                if let hotel = places.first {
                    self?.setCenter(for: hotel)
                    self?.findCoffee(near: hotel)
                }
            case .failure(let error):
                print("Search failed with: \(error)")
                
            }
        }
    }
}

努力の結晶を見るときが来ました。アプリケーションをビルドし、実行しましょう!

検索バーに住所やホテル名を入力し、go をタップします。例えば、1800 Yale Avenue, Seattle を検索すると、地図センターが検索結果の1番目に移動し、地図上にコーヒーの結果のマーカーが表示されるのがわかります。マーカーをタップすると、その場所の情報が表示されます。

ヒント:デフォルトの青いマーカーは、好きな画像に置き換えることができます。ここでは、スマイリーフェイスをマーカーとして使用する例をご紹介します : )

AMLMapView(mapState: viewModel.mapState)
  .featureImage { 
    UIImage(
      systemName: "face.smiling",
      withConfiguration: UIImage.SymbolConfiguration(pointSize: 40)
    )!
  }
  .edgesIgnoringSafeArea(.all)

まとめ

これで、住所や場所の近くにあるコーヒーショップを表示する機能的なアプリを構築し、その過程で Amplify Geo のいくつかの素晴らしい機能を学ぶことができました。Amplify Geo にはまだまだたくさんの機能とカスタマイズがあるので、以下の「次のステップ」のセクションでご紹介します。

次のステップ

さて、beans プロジェクトにいくつかの機能を追加しましたが、次のステップは完全にあなた次第です。今回構築したアプリケーションをすべて削除する場合はamplify delete を使用してバックエンドリソースを削除し、beans プロジェクトがクリーンアップされることを確認してください。beans の上に構築し続け、評価によるソーシャル要素を追加したいですか?その場合は、Amplify Geo と Amplify DataStore を組み合わせて使うことで、素晴らしい機能を構築することができます。

詳細に関しては、公式の Amplify GeoドキュメントAmplify MapLibre API ドキュメントをご参照ください。また、より複雑な UI/UX を構築するためのインスピレーションを得るために、AMLExamples をご覧になってみてください。私たちは、Geo カテゴリの新機能に積極的に取り組んでいますので、新しいエキサイティングな機能に注目してください。

この記事は、Add Maps to your iOS App using Amplify Geo, powered by Amazon Location Service を翻訳したものです。
この記事のオリジナルは、AWS Amplify のソフトウェア開発エンジニアである Ian Saultz によって書かれました。
翻訳は Solutions Architect の 稲田大陸 が担当しました。