投稿の経緯
SwiftUIでMapを表示するにはこちらにもあるようにUIViewRepresentable
を使ってカスタムViewを作成しなければなりませんでした。
しかし、iOS14からMapというSwiftUI用のViewが標準で搭載され、簡易的に実装することができるようになったので、個人開発中のアプリで使ってみたので、投稿します。
環境
Swift version 5.4.2
Xcode version 12.5.1
MapKitをインポート
SwiftUIファイルを作成し(今回はMapView)、MapKitをインポートします。
import MapKit
必要な変数の準備
今回MapKitを扱うにあたって使用するイニシャライザはCLLocationCoordinate2D
とMKCoordinateSpan
です。なおこちら2点を扱うにあたってMKCoordinateRegion
という状態変数が必要です。
各イニシャライザを要約して説明すると...
CLLocationCoordinate2D
表示領域の中心位置を緯度経度で決める
MKCoordinateSpan
緯度・経度それぞれに対する表示領域をそれぞれの縮尺(単位は度)で指定します。1度で約111kmあるそうなので比較的小さい値を指定することが多いと思います。例えば、100m を縮尺で表すと 0.0009 くらいになります。
また、引数として緯度経度を保存するDouble型
の変数を用意します。
緯度 latitude
経度 longitude
👇以下ここまでのコードです👇
import SwiftUI
import MapKit
struct MapView: View {
@State private var region = MKCoordinateRegion() // 座標領域
var coordinate: CLLocationCoordinate2D? // 表示領域の中心位置
var latitude: Double // 緯度
var longitude: Double // 経度
/////以下省略/////
}
Mapを使ってマップを表示
続いてMapを使ってViewに反映させるので、まずはコードを見ていただきます。
Map(coordinateRegion: $region,
interactionModes: .all,
showsUserLocation: true,
userTrackingMode: $userTrackingMode,
annotationItems: [
PinItem(coordinate: .init(latitude: latitude, longitude: longitude))
],
annotationContent: { item in
MapMarker(coordinate: item.coordinate)
})
少し複雑に見えますが、難しくはないのでひとつひとつ解説します。
coordinateRegion
MKCoordinateRegion
の状態変数をバインディング指定します。(@State or @Published)
interactionModes
ユーザー操作の許可を以下の3つから設定します。
.pan
スワイプ(ドラッグ)による操作を許可。
.zoom
ダブルタッチ or ピンチ操作による拡大・縮小の操作を許可。
.all
.pan と .zoom の両方を許可。
showsUserLocation
true
でユーザーの現在位置を表示。
userTrackingMode
マップがユーザーの現在位置を追跡させるどうかを状態変数で管理します。
.follow
ユーザーを追跡します。
.none
ユーザーの追跡を停止します。
以下のように状態変数を準備して、指定してあげれば良いです。
@State private var userTrackingMode: MapUserTrackingMode = .none
annotationItems
マップ上で表示するピンの位置を緯度経度で指定します。ピンはIdentifiable
に準拠した構造体で定義し、CLLocationCoordinate2D型
の緯度経度を管理させます。
struct PinItem: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}
annotationContent
annotationItemsのピンのデザインを決めます。MapMarker
、MapPin
の二択です。
👇以下ここまでのコードです👇
import SwiftUI
import MapKit
struct PinItem: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}
struct MapView: View {
@State private var region = MKCoordinateRegion() // 座標領域
@State private var userTrackingMode: MapUserTrackingMode = .none
var coordinate: CLLocationCoordinate2D? // 表示領域の中心位置
var latitude: Double // 緯度
var longitude: Double // 経度
var body: some View {
Map(coordinateRegion: $region,
interactionModes: .all,
showsUserLocation: true,
userTrackingMode: $userTrackingMode,
annotationItems: [
PinItem(coordinate: .init(latitude: latitude, longitude: longitude))
],
annotationContent: { item in
MapMarker(coordinate: item.coordinate)
})
}
}
引数で取得した緯度経度を使って表示領域の中心位置と縮尺を決める
private func setRegion(coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.0009, longitudeDelta: 0.0009)
)
}
プライベートな関数を用意して引数にCLLocationCoordinate2D
を用意し、受け取った値は、状態変数region
のcenter
に指定。(ここで表示領域の中心位置が決まる)
次に、span
にMKCoordinateSpan
を指定し縮尺度合いを決定する。
そして、先ほど記述したMap
に.onAppear
を指定し、MapViewが呼ばれたら、まずsetRegion
が呼ばれるように調整する。
※.onAppear
はswiftでいうviewDidAppearのような扱いです。
👇以下ここまでのコードです👇
import SwiftUI
import MapKit
struct PinItem: Identifiable {
let id = UUID()
let coordinate: CLLocationCoordinate2D
}
struct MapView: View {
@State private var region = MKCoordinateRegion() // 座標領域
@State private var userTrackingMode: MapUserTrackingMode = .none
var coordinate: CLLocationCoordinate2D? // 表示領域の中心位置
var latitude: Double // 緯度
var longitude: Double // 経度
var body: some View {
Map(coordinateRegion: $region,
interactionModes: .all,
showsUserLocation: true,
userTrackingMode: $userTrackingMode,
annotationItems: [
PinItem(coordinate: .init(latitude: latitude, longitude: longitude))
],
annotationContent: { item in
MapMarker(coordinate: item.coordinate)
})
.onAppear {
setRegion(coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude))
}
}
// 引数で取得した緯度経度を使って動的に表示領域の中心位置と、縮尺を決める
private func setRegion(coordinate: CLLocationCoordinate2D) {
region = MKCoordinateRegion(center: coordinate,
span: MKCoordinateSpan(latitudeDelta: 0.0009, longitudeDelta: 0.0009)
)
}
}
別ファイルからMapViewを呼び出す
MapView(latitude: <Double>, longitude: <Double>)
となるので、latitudeに緯度
を、longitudeに経度
を指定してあげれば、MapViewの.onAppear
が呼ばれ、setRegion
の中で表示領域の中心位置と、縮尺が決定され、Viewが更新されます。
お知らせ
現在、iOS開発案件を業務委託で募集中です(副業)。TwitterDMでご依頼をお待ちしています。