質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.65%

Swift 経路を表示したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 108

globalplus

score 61

このページこのページを参考にしました
独自に座標を取得して逆ジオコーディングをしピンを置き、ピンをタップした吹き出しの中に経路ボタンを実装した所までは出来たのですが、経路ボタンを押すと

下記のコード内の
// 現在地と目的地を含む表示範囲を設定する
self.showUserAndDestinationOnMap()
} as! MKDirections.DirectionsHandler)
にThread 1: signal SIGABRTが出てしまいます

変数userLocationとdestLocationを強制アンラップしているからアプリが落ちてしまうのではないかと予測を立てていますが、はっきりとした原因はわかっていません。
アドバイス頂ければ嬉しいです。よろしくお願いします。

class FirstViewController: UIViewController, CLLocationManagerDelegate {

    @IBOutlet weak var mapView: MKMapView!
    let locationManager = CLLocationManager()
    let geocoder = CLGeocoder()
    let annotation = MKPointAnnotation()
    var userLocation: CLLocationCoordinate2D!
    var destLocation: CLLocationCoordinate2D!

    let regionRadius: CLLocationDistance = 1000

    override func viewDidLoad() {
        super.viewDidLoad()
        locationManager.delegate = self as CLLocationManagerDelegate
        self.mapView.delegate = self
    }
    func geocoding()
    {       
        //逆ジオコーディング
        let location = CLLocation(latitude: {doubleLatitude(独自に取得した緯度です)}(), longitude: {doubleLongitude(独自に取得した経度です)}())
        CLGeocoder().reverseGeocodeLocation(location) { placemarks, error in
            guard let placemark = placemarks?.first, error == nil else { return }
            self.destLocation = CLLocationCoordinate2DMake(placemark.location!.coordinate.latitude, placemark.location!.coordinate.longitude)
            print("逆ジオコーディングの中")

            //ピン
            self.annotation.coordinate = CLLocationCoordinate2DMake(doubleLatitude, doubleLongitude);self.mapView.addAnnotation(self.annotation)
            self.mapView.addAnnotation(self.annotation)
            print("ピンを置きました")

            //アノテーション
            let artwork = Artwork(title: "後で決める",
              locationName: "後で決める",
              discipline: "後で決める",
              coordinate: CLLocationCoordinate2D(latitude: doubleLatitude, longitude: doubleLongitude))
            self.mapView.addAnnotation(artwork)

        }
        })
    }

    //経路表示
    @objc func buttonEvent(_ sender: UIButton)
    {
        // 現在地と目的地のMKPlacemarkを生成
        let fromPlacemark = MKPlacemark(coordinate:userLocation, addressDictionary:nil)
        let toPlacemark   = MKPlacemark(coordinate:destLocation, addressDictionary:nil)

        // MKPlacemark から MKMapItem を生成
        let fromItem = MKMapItem(placemark:fromPlacemark)
        let toItem   = MKMapItem(placemark:toPlacemark)

        // MKMapItem をセットして MKDirectionsRequest を生成
        let request = MKDirections.Request()

        request.source = fromItem
        request.destination = toItem
        request.requestsAlternateRoutes = false // 単独の経路を検索
        request.transportType = MKDirectionsTransportType.any

        let directions = MKDirections(request:request)
        directions.calculate(completionHandler: {
            (response:MKDirections.Response!, error:NSError!) -> Void in

            response.routes.count
            if (error != nil || response.routes.isEmpty) {
                return
            }
            let route: MKRoute = response.routes[0] as MKRoute
            // 経路を描画
            self.mapView.addOverlay(route.polyline)
            // 現在地と目的地を含む表示範囲を設定する
            self.showUserAndDestinationOnMap()
            //Thread 1: signal SIGABRTのエラーです(エラー内容は下記に記載↓)
            } as! MKDirections.DirectionsHandler)
    }
    //地図上に現在地、目的地、経路がちょうど収まるように表示
    func showUserAndDestinationOnMap()
    {
        // 現在地と目的地を含む矩形を計算
        let maxLat:Double = fmax(userLocation.latitude,  destLocation.latitude)
        let maxLon:Double = fmax(userLocation.longitude, destLocation.longitude)
        let minLat:Double = fmin(userLocation.latitude,  destLocation.latitude)
        let minLon:Double = fmin(userLocation.longitude, destLocation.longitude)

        // 地図表示するときの緯度、経度の幅を計算
        let mapMargin:Double = 1.5;  // 経路が入る幅(1.0)+余白(0.5)
        let leastCoordSpan:Double = 0.005;    // 拡大表示したときの最大値
        let span_x:Double = fmax(leastCoordSpan, fabs(maxLat - minLat) * mapMargin);
        let span_y:Double = fmax(leastCoordSpan, fabs(maxLon - minLon) * mapMargin);

        let span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: span_x, longitudeDelta: span_y);

        // 現在地を目的地の中心を計算
        let center:CLLocationCoordinate2D = CLLocationCoordinate2DMake((maxLat + minLat) / 2, (maxLon + minLon) / 2);
        let region:MKCoordinateRegion = MKCoordinateRegion(center: center, span: span);

        mapView.setRegion(mapView.regionThatFits(region), animated:true);
    }
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus)
    {
        switch status {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .authorizedWhenInUse:
            locationManager.startUpdatingLocation()
        default:
            break
        }
    }
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
    {
        //無理やり!でアンラップしました←ここが間違っている?
        userLocation = CLLocationCoordinate2DMake(manager.location!.coordinate.latitude, manager.location!.coordinate.longitude)

        if let coordinate = locations.last?.coordinate {
            // 現在地を拡大して表示する
            let span = MKCoordinateSpan(latitudeDelta: 0.01, longitudeDelta: 0.01)
            let region = MKCoordinateRegion(center: coordinate, span: span)
            mapView.region = region

        }
        guard let newLocation = locations.last else {
                return
        }
        self.latitude.text = "".appendingFormat("%.4f", newLocation.coordinate.latitude)
        self.longitude.text = "".appendingFormat("%.4f", newLocation.coordinate.longitude)
        let location = locations.first
        let latitude = location?.coordinate.latitude
        let longitude = location?.coordinate.longitude

        print("緯度: \(latitude!)\n経度: \(longitude!)")

    }
    // 位置情報取得に失敗した時に呼び出されるデリゲート.
    func locationManager(manager: CLLocationManager!,didFailWithError error: NSError!){
        print("locationManager error")
    }
    func centerMapOnLocation(location: CLLocation)
    {
        let coordinateRegion = MKCoordinateRegion(center: location.coordinate,
                                                  latitudinalMeters: regionRadius, longitudinalMeters: regionRadius)
      mapView.setRegion(coordinateRegion, animated: true)
    }


}

extension FirstViewController: MKMapViewDelegate {
  // 1
  func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
  {
    // 2
    guard let annotation = annotation as? Artwork else { return nil }
    // 3
    let identifier = "marker"
    var view: MKMarkerAnnotationView
    // 4
    if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
      as? MKMarkerAnnotationView {
      dequeuedView.annotation = annotation
      view = dequeuedView
    } else {
      // 5
      view = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)
      view.canShowCallout = true
      view.calloutOffset = CGPoint(x: -5, y: 5)
      //右ボタンをアノテーションビューに追加する。
      let button = UIButton()
      button.frame = CGRect(x: 0,y: 0,width: 70,height: 50)
      button.setTitle("経路", for: .normal)
      button.backgroundColor = UIColor.green
      button.setTitleColor(UIColor.white, for:.normal)
      button.addTarget(self, action: #selector(buttonEvent(_:)), for: UIControl.Event.touchUpInside)
      view.rightCalloutAccessoryView = button
    }
    return view
  }

}

エラー内容
Could not cast value of type '(Swift.Optional<C.MKDirectionsResponse>, Swift.Optional<C.NSError>) -> ()' (0x1e172caf8) to '(Swift.Optional<__C.MKDirectionsResponse>, Swift.Optional<Swift.Error>) -> ()' (0x1e172cc30).

修正後コード

directions.calculate(completionHandler:
            {
                (response:MKDirections.Response?, error:Error?) -> Void in
                response!.routes.count
                if (error != nil || response!.routes.isEmpty) {
                    return
                }
                let route: MKRoute = response!.routes[0] as MKRoute
                // 経路を描画
                self.mapView.addOverlay(route.polyline)
                // 現在地と目的地を含む表示範囲を設定する
                self.showUserAndDestinationOnMap()
            })
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

ソース全部読むのが面倒なので、エラーの原因だけ書きますが、

calculate(completionHandler:)completionHandlerに渡すクロージャの型は

https://developer.apple.com/documentation/mapkit/mkdirections/directionshandler

によると
typealias DirectionsHandler = (MKDirections.Response?, Error?) -> Void
と定義されています。

一方、あなたの実装は
(response:MKDirections.Response!, error:NSError!) -> Void
となっており、型が一致しません。

おそらくどこからのコピペでしょうが、それを無理矢理 as! MKDirections.DirectionsHandlerと強制的にキャストして対応した方法も間違っています。

正しい解決方法は型を揃えることです。
つまり

directions.calculate(completionHandler: {
(response:MKDirections.Response!, error:NSError!) -> Void in

directions.calculate(completionHandler: {
(response:MKDirections.Response?, error:Error?) -> Void in

もしくは

directions.calculate(completionHandler: { response, error in

などと省略形で書いても良いとは思います。
as! MKDirections.DirectionsHandlerは不要です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/12/25 12:44 編集

    ありがとうございます。説明通りに書き直しました。
    エラーは消えたのですが、経路ボタンを押しても経路が描画されません。
    もしよかったらまた教えて頂けませんか?

    修正後コードを記載しました

    キャンセル

  • 2019/12/25 16:10

    buttonEvent関数が呼ばれているかどうかブレークポイントを張るなりしてどこまで期待通りに動作しているか一つ一つ確認してください。

    キャンセル

  • 2019/12/25 20:25

    self.showUserAndDestinationOnMap()の下に
    print("経路を描画しました")を記述し、コンソールを確認した所、
    経路を描画しましたと出力されました。

    という事はself.showUserAndDestinationOnMap()が動いているという意味だと思うのですが実際に経路は描画されていません。

    他に確認すべきところはありますか?

    キャンセル

  • 2019/12/26 06:18

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
    let renderer = MKPolylineRenderer(overlay: overlay)
    renderer.strokeColor = UIColor.blue
    renderer.lineWidth = 4.0
    return renderer
    }
    を追加したら経路が表示されました。

    takabosoftさんの回答がなければ辿りつかなかったのでベストアンサーをさせていただきます。

    ありがとうございました。よかったらまたよろしくお願いします。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.65%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る