10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[SwiftUI]iOS14から用意されたMapを使って動的なMapを開発してみる

Posted at

投稿の経緯

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を扱うにあたって使用するイニシャライザはCLLocationCoordinate2DMKCoordinateSpanです。なおこちら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のピンのデザインを決めます。MapMarkerMapPinの二択です。

👇以下ここまでのコードです👇

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を用意し、受け取った値は、状態変数regioncenterに指定。(ここで表示領域の中心位置が決まる)
次に、spanMKCoordinateSpanを指定し縮尺度合いを決定する。

そして、先ほど記述した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でご依頼をお待ちしています。

10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?