📍

【Swift】MapKit でストリートビューを実装する

2024/04/10に公開

初めに

今回は SwiftUI と MapKit でストリートビュー(LookAroundViewer)を実装してみたいと思います。
なお、今回は visionOS 上で動作させていますが、のページに飛ぶと以下に対応していることがわかり、iOSでも使用できるかと思います。

  • iOS 17.0+
  • iPadOS 17.0+
  • visionOS 1.0+

記事の対象者

  • Swift, SwiftUI学習者
  • アプリに LookAroundViewer を導入したい方

目的

今回は上記の通り MapKit で LookAroundViewer を実装することを目的とします。
最終的には以下のようにマップからポイントを指定して、その場所の LookAroundViewer を確認できるような実装を行いたいと思います。

https://youtu.be/-usjw2OxwJ4

実装

シンプルな実装

初めに LookAroundViewer を実装した動くコードを以下に提示します。
ContentView と入れ替えればそのまま動作するかと思います。

import SwiftUI
import MapKit

struct LookAroundView: View {
    @State private var position: MapCameraPosition = .region(.init(
        center: .init(latitude: 34.687234, longitude: 135.525842),
        span: .init(latitudeDelta: 0.005, longitudeDelta: 0.005)
    ))
    @State private var lookAroundScene: MKLookAroundScene?
    @State private var isLookAroundPreviewShown = false
    
    var body: some View {
        MapReader { mapProxy in
            Map(position: $position)
                .onTapGesture { location in
                    guard let selectedLocation = mapProxy.convert(location, from: .local) else { return }
                    requestLookAroundScene(selectedLocation: selectedLocation)
                    isLookAroundPreviewShown = true
                }
        }
        .lookAroundViewer(
            isPresented: $isLookAroundPreviewShown,
            scene: $lookAroundScene
        )
    }
    
    func requestLookAroundScene(selectedLocation: CLLocationCoordinate2D) {
        Task {
            let request = MKLookAroundSceneRequest(coordinate: selectedLocation)
            lookAroundScene = try? await request.scene
        }
    }
}

それぞれ詳しくみていきます。

以下の部分ではそれぞれマップのカメラ位置、LookAroundViewer のシーン、表示非表示の初期値を設定しています。
position はマップのカメラ位置であり、初期値で大阪城付近に設定しています。
lookAroundScene はユーザーがマップをタップすることで位置を取得するため、 Nullable な State としています。
isLookAroundPreviewShownLookAroundViewer の表示非表示を保持する State であり、初期値では表示されないため false としています。

@State private var position: MapCameraPosition = .region(.init(
  center: .init(latitude: 34.687234, longitude: 135.525842),
  span: .init(latitudeDelta: 0.005, longitudeDelta: 0.005)
))
@State private var lookAroundScene: MKLookAroundScene?
@State private var isLookAroundPreviewShown = false

以下の部分ではタップした際に LookAroundViewer が開くマップを実装しています。
MapReadermapProxy を受け取り、それを onTapGesture で使用することでタップした位置を取得することができます。
また、後述の requestLookAroundScene にタップした位置を渡して isPresentedLookAroundPreview を true にしています。

MapReader { mapProxy in
  Map(position: $position)
    .onTapGesture { location in
      guard let selectedLocation = mapProxy.convert(location, from: .local)
        else { return }
      requestLookAroundScene(selectedLocation: selectedLocation)
      isPresentedLookAroundPreview = true
  }
}

以下では LookAroundViewer の設定を行なっています。
onTapGesture を持つ MapReader に対して .lookAroundViewer を指定することで、タップした際に LookAroundViewer を開く実装にすることができます。

.lookAroundViewer(
  isPresented: $isLookAroundPreviewShown,
  scene: $lookAroundScene
)

以下ではマップでタップされた場所を CLLocationCoordinate2D として受け取り、その座標をもとにして LookAroundViewer の scene を生成しています。

func requestLookAroundScene(selectedLocation: CLLocationCoordinate2D) {
  Task {
    let request = MKLookAroundSceneRequest(coordinate: selectedLocation)
    lookAroundScene = try? await request.scene
  }
}

なお取得した座標から scene を生成できなかった場合は以下のようにグレーの表示になります。

マップの設定

マップに関して以下のように指定することができます。

.lookAroundViewer(
  isPresented: $isLookAroundPreviewShown,
  scene: $lookAroundScene,
  allowsNavigation: false,
  showsRoadLabels: false
)

allowsNavigation はデフォルトでは true になっています。
これを false にすると初めの章で提示した動画のように周りをタップして歩き回ることができなくなります。

また、showsRoadLabels はデフォルトで true になっていますが、 false にすると以下の画像のように町の名前や道路の名前が表示されなくなります。

showsRoadLabels: true showsRoadLabels: false
赤く囲った部分に「谷町2」のテキストが表示されています テキストは表示されません

今回はマップのカメラ位置を固定して実装しましたが、前の画面から座標を受け取り、 onAppearposition に代入することで任意の座標をカメラ位置に指定することができます。

まとめ

最後まで読んでいただいてありがとうございました。

今回実装した LookAroundViewer では比較的簡単にストリートビューと同じような機能を実装することができました。また、 visionOS ではかなり画面を拡大することができ、より没入感のあるストリートビューを実装できます。

誤っている点やもっと良い書き方があればご指摘いただければ幸いです。

参考

https://developer.apple.com/documentation/mapkit/lookaroundpreview/4231402-lookaroundviewer

https://qiita.com/SNQ-2001/items/ae0fd2ff2a415d7e0713

Discussion