【SwiftUI】MapKitで所要時間と距離数を表示させる方法!時間と分単位に調整

この記事からわかること

  • Swift UIMapKitフレームワーク使い方
  • 2地点間経路所要時間距離数表示させる方法
  • 所要時間を時間単位に直す方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

SwiftUIでMapKitフレームワークを使用して地図上に経路を表示させる方法をまとめていきたいと思います。

MapKitフレームワークの使い方やポイントが分からない方は下記リンクを参考にしてください。

前提と環境

今回はSwiftUIフレームワークを使用している場合に経路の所要時間と距離数を取得し、表示させる方法をまとめていきます。

SwiftUIでは2022年8月時点で経路を表示させる方法がなさそう(私が知らないだけかも?)なのでUiKitで経路ビューを構築してUIViewRepresentableプロパティを使ってSwiftUIで使用できるようにしています。実装方法は下記記事を参考にしてください。またこの記事も下記記事の続きになっていますのでご注意ください。

注意:SwiftUIとUIKitのMapKitの使い方は少し異なります。今回は経路表示を実装するためにUIKitの場合の地図表示方法で進んでいきますが地図を表示するだけであればSwiftUIのMapView構造体で簡単に表示できますのでこちらの記事をご覧ください。

class MKMapView : UIView // UIKit
public struct Map<Content> : View where Content : View  // SwiftUI

所要時間と距離数をSwiftUIで表示する

今回の目標は以下のように所要時間と距離数を上部に表示するビューを作成していきます。

SwiftUI】MapKitで地図に経路を表示させる方法!所要時間と距離数をSwiftUIで表示

今回の作業の流れとポイント

  1. ビュー構造体(UIViewRepresentableに準拠)を拡張してCoordinatorクラスを作成
  2. SwiftUIのビュー・ビュー構造体・Coordinatorクラスの3つにプロパティを作成
  3. MKRouteのプロパティから所要時間と距離数を取得してプロパティに格納
  4. ビュー側で表示の整形

Coordinatorクラスの作成

まずはUIViewRepresentableプロトコルを準拠させた構造体を拡張してCoordinatorクラスを追加します。


struct UIMapView: UIViewRepresentable {
    func makeUIView(context: Self.Context) -> MKMapView {
      // 省略
    }
    func updateUIView(_ uiView: MKMapView, context: Self.Context) {
        // 省略
    }
    // makeCoordinatorメソッドを追加
    func makeCoordinator() -> Coordinator {
      Coordinator(self,expectedTravelTime: $expectedTravelTime,distance: $distance)
    }
} 
// Coordinatorクラスを拡張して追加
extension UIMapView{
    class Coordinator: NSObject {
        var control: UIMapView
        @Binding  var expectedTravelTime:Double // 所要時間 秒単位
        @Binding  var distance:Double // 距離数  m単位
        
        init(_ control: UIMapView,expectedTravelTime:Binding<Double>,distance:Binding<Double>){
          self.control = control
            // Binding型はアンダースコア(_)をつける
            _expectedTravelTime  = expectedTravelTime
            _distance = distance
        }
    }
}

Coordinatorクラスの中には所要時間と距離数を格納するプロパティをビュー構造体と連携できるように@Bindingを使って宣言します。イニシャライザで各プロパティに受け取った値を格納できるようにしておきます。@Bindingを使用したプロパティには名前の先頭に( _:アンダースコア)をつけないとエラーになるので注意してください。

Coordinatorクラスを定義したらインスタンス化するためのmakeCoordinatorメソッドの定義が必要になります。その際にビュー構造体のプロパティをバインディングして渡します。(この時点ではまだ未定義です)

最初はわざわざCoordinatorクラスを実装しなくても「構造体の中にプロパティを用意すればいけるかも」と思いましたが、makeUIViewメソッドの中でプロパティを更新するかつ(@Stateかmutatingが必須)、SwiftUIのビューのプロパティともバインディングさせたいためこのような形になりました。

各構造体(クラス)にプロパティを準備する

Coordinatorクラスだけではなく、ビュー構造体(UIMapView)とSwiftUiのビュー(ContentView)にもプロパティを定義しておきます。


struct UIMapView: UIViewRepresentable {
  let Manager = MapManager()

  @Binding  var expectedTravelTime:Double // 所要時間 秒単位
  @Binding  var distance: Double          // 距離数 m単位

  // 以下省略
  }

import SwiftUI
import MapKit

struct ContentView: View {

    @State  var expectedTravelTime:Double = -1  // 所要時間
    @State  var distance: Double = 0            // 距離数

    var body: some View {
        UIMapView()
    }
}

@State@Bindingを使い分けながら宣言しておきます。これで以下のような関係性のプロパティ構造が出来上がります。

 大元  ↔︎ 橋渡し ↔︎ 実際に値が格納される
ContentView:expectedTravelTime ↔︎ UIMapView:expectedTravelTime ↔︎ Coordinator:expectedTravelTime
ContentView:distance ↔︎ UIMapView:distance ↔︎ Coordinator:distance

MKRouteのプロパティから所要時間と距離数を取得

2地点間の経路の所要時間と距離数はMKRouteのプロパティから取得できます。

MKRouteのプロパティ

polyline           // 経路を示す線(オブジェクト)
name               // ルートの名称
distance           // 距離(メートル単位)
expectedTravelTime // 所要時間

func makeUIView(context: Self.Context) -> MKMapView {
// 省略〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
    // ルートを取得
    let route = directionResonse.routes[0]

    // 以下を追加
    // 所用時間と距離を格納
    context.coordinator.expectedTravelTime = route.expectedTravelTime
    context.coordinator.distance = route.distance

    // ビューにオーバーレイオブジェクトを追加
    mapView.addOverlay(route.polyline, level: .aboveRoads)

定義したCoordinatorクラスのプロパティにはmakeUIViewメソッドの引数で受け取るcontextcoordinatorプロパティからアクセスできます。これでプロパティに格納した値が伝播し、ContentViewのプロパティからも同値を参照することができるようになりました。

各プロパティを整形する

取得した値を出力すると以下のように秒単位の時間とメートル単位の距離数になります。ユーザー視点からするとだいぶ不親切なので認識しやすい単位へと変換させる処理を実装していきます。

print(route.expectedTravelTime)
// 結果:20417.0 秒 → 5時間40分(希望)
print(route.distance) 
// 結果:507203.0 m → 507.2km(希望)

所要時間を時間と分に直す

秒単位の時間を分に直すには60秒(=1分)で割り算時間に直すには3600秒(60秒×60分)で割り算すればOKです。

20417.0 秒 ÷ 60秒 = 約340分
20417.0 秒 ÷ ( 60秒 × 60分 ) = 約5.67時間

今回は秒数の時間を受け取った時に所要時間を返すformatTimeメソッドを自作していきます。expectedTravelTimeプロパティの初期値には-1を与えたので初期値のままなら「経路を検索中...」0以上の値ならその時間に応じた形式で返すようにswitch文を使って分岐させました。

func formatTime(_ time:Double) -> String{
    switch time {
    case -1 :
        return "経路を検索中..."
    case 0..<60 :
        return String(time) + "秒"
    case 0..<3600 :
        return String(format: "%.0f", time/60) + "分"
    default:
        let hour = Int(time/3600)
        let minutes = (time - Double(hour * 3600))/60
        return String(hour) + "時間" + String(format: "%.0f", minutes)  + "分"
    }
}

小数点以下の値を表示する際はString(format:)イニシャライザを使用すると便利です。切り上げや切り捨てなどの丸め処理においては以下の記事を参考にしてください。

距離数をkm単位に整形する

距離数に関してはメートル単位で取得できるのでkmにするために1000m(=1km)で割り算するだけです。

507203.0 m ÷ 1000m = 507.2km

あとはSwift UIのビュー側で表示するだけです。


struct ContentView: View {
    @State  var expectedTravelTime:Double = -1  // 所要時間
    @State  var distance: Double = 0            // 距離数
    
    func formatTime(_ time:Double) -> String{
        switch time {
        case -1 :
            return "経路を検索中..."
        case 0..<60 : // 1分以下
            return String(time) + "秒"
        case 0..<3600 : // 1時間以下
            return String(format: "%.0f", time/60) + "分"
        default: // 1時間以上
            let hour = Int(time/3600)
            let minutes = (time - Double(hour * 3600))/60
            return String(hour) + "時間" + String(format: "%.0f", minutes)  + "分"
        }
    }
    
    var body: some View {
        VStack{
            HStack{
                Spacer()
                Image(systemName:"clock")
                Spacer()
                Text("\(formatTime(expectedTravelTime))").frame(width: 200)
                Spacer()
            }
            HStack{
                Spacer()
                Image(systemName:"arrow.triangle.turn.up.right.circle")
                Spacer()
                Text(String(format: "%.1fkm", distance/1000)).frame(width: 200)
                Spacer()
            }
            UIMapView(expectedTravelTime: $expectedTravelTime,distance: $distance)
        }
    }
}
SwiftUI】MapKitで地図に経路を表示させる方法!所要時間と距離数をSwiftUIで表示

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

私がSwift UI学習に使用した参考書

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index