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

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

ただいまの
回答率

89.86%

Swift3でMapViewのPinを縮小した写真にしたい

解決済

回答 5

投稿

  • 評価
  • クリップ 1
  • VIEW 1,252

Robokun

score 36

MapViewを使い、現在地で撮った写真をPinの画像にするフォトアルバムを作ろうとしています。
pickerViewで写真を撮った後に画像の配列に写真を入れて、それをPinの画像にしようとしています。
コードは以下のようにしました。
PickerViewを起動し、写真を撮るまではOKなのですが、撮り終わった後に

fatal error: Index out of range
というエラーメッセージで落ちてしまいます。
おそらく画像をUIimageの配列に入れる処理が悪いのだと思うのですが
解決方法が分かりません。
修正するヒントなどありましたら教えていただければと思います。

import UIKit
import CoreLocation
import MapKit

class MapViewController: UIViewController {

    @IBOutlet weak var mapView: MKMapView!
    @IBOutlet weak var locationLabel: UILabel!

    var locationManager = CLLocationManager()
    var locationData:CLLocation?
    var imageArray: Array<UIImage> = Array<UIImage>()

    override func viewDidLoad() {
        super.viewDidLoad()

        //ロケーションの状態表示を使用不可にしておく
        disableLocationLabel()

        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.distanceFilter = 1.0
        mapView.setUserTrackingMode(.follow, animated: true)

        locationManager.delegate = self
        mapView.delegate = self
        mapView.showsScale=true
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    //画面呼び出し後のメソッド
    override func viewDidAppear(_ animated: Bool) {

        if(CLLocationManager.locationServicesEnabled() == true){
            switch CLLocationManager.authorizationStatus() {

            //未設定の場合
            case CLAuthorizationStatus.notDetermined:
                locationManager.requestWhenInUseAuthorization()

            //機能制限されている場合
            case CLAuthorizationStatus.restricted:
                alertMessage(message: "位置情報サービスの利用が制限されている利用できません。「設定」⇒「一般」⇒「機能制限」")

            //「許可しない」に設定されている場合
            case CLAuthorizationStatus.denied:
                alertMessage(message: "位置情報の利用が許可されていないため利用できません。「設定」⇒「プライバシー」⇒「位置情報サービス」⇒「アプリ名」")

            //「このAppの使用中のみ許可」に設定されている場合
            case CLAuthorizationStatus.authorizedWhenInUse:
                //位置情報の取得を開始する。
                locationManager.startUpdatingLocation()

            //「常に許可」に設定されている場合
            case CLAuthorizationStatus.authorizedAlways:
                //位置情報の取得を開始する。
                locationManager.startUpdatingLocation()
            }

        } else {
            //位置情報サービスがOFFの場合
            alertMessage(message: "位置情報サービスがONになっていないため利用できません。「設定」⇒「プライバシー」⇒「位置情報サービス」")
        }
    }

    //表示を使用不可に変更
    func disableLocationLabel(){
        locationLabel.text = "GPSが使用許可になっていません"
    }

    //警告アラートを表示
    func alertMessage(message:String) {
        let aleartController = UIAlertController(title: "注意", message: message, preferredStyle: .alert)
        let defaultAction = UIAlertAction(title:"OK", style: .default, handler:nil)
        aleartController.addAction(defaultAction)

        present(aleartController, animated:true, completion:nil)
    }

    //写真ボタンを押した時
    @IBAction func takePhoto(_ sender: UIButton) {

        //カメラが使えるか確認する
        if UIImagePickerController.isSourceTypeAvailable(.camera){
            //ImagePickerを表示する
            let cameraPicker = UIImagePickerController()
            cameraPicker.sourceType = UIImagePickerControllerSourceType.camera
            cameraPicker.delegate = self
            self.present(cameraPicker, animated: true, completion: nil)
        }
    }

    //戻るボタンを押した時
    @IBAction func backButton(_ sender: UIButton) {
        self.dismiss(animated: true, completion: nil)
    }
}

//MARK: CLLocationManagerDelegate
extension MapViewController:CLLocationManagerDelegate{

    //位置情報が更新された時に呼ばれる
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

        //Locationの最後の値を取り出す
        locationData = locations.last

        //緯度
        if var ido = locationData?.coordinate.latitude{
            ido = round(ido * 1000000)/1000000
        }
        //経度
        if var keido = locationData?.coordinate.longitude{
            keido = round(keido * 1000000)/1000000
        }
        //標高
        if var hyoukou = locationData?.altitude{
            hyoukou = round(hyoukou*100)/100
        }

        //MAP中央の座標
        let center = CLLocationCoordinate2D(latitude: (locationData?.coordinate.latitude)!, longitude: (locationData?.coordinate.longitude)!)

        //地図のセンターに設定する
        mapView.setCenter(center, animated: true)

        //ロケーション表示(デバッグ用)
        locationLabel.text = "GPS:\(center.latitude)  \(center.longitude)"

        //500m四方を表示する
        let myRegion : MKCoordinateRegion = MKCoordinateRegionMakeWithDistance(center, 500, 500)
        mapView.setRegion(myRegion, animated: true)

    }

    //位置情報のステータスが変わった時
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {

        switch status{
        //利用許可に変更された
        case .authorizedAlways,.authorizedWhenInUse:
            locationManager.startUpdatingLocation()
        //利用不可に変更された
        case .denied:
            locationManager.stopUpdatingLocation()
            disableLocationLabel()

        default://制限されているか、ユーザーが決定していない、その他
            //ロケーションの更新を停止する
            locationManager.stopUpdatingLocation()

            //トラッキングモードをnoneにする
            mapView.setUserTrackingMode(.none, animated: true)
            disableLocationLabel()
        }
    }
}

//MapViewのDelegate
extension MapViewController:MKMapViewDelegate{

    //ピンを表示する時の処理
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

        // 現在地は外す
        if annotation is MKUserLocation {
            return nil
        }

        let pinID = "PIN"
        var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: pinID) as? MKPinAnnotationView
        //Annotationが見つからなかった場合は新しく作成
        if annotationView == nil {
            annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pinID)

            let size: CGSize = CGSize(width: 42, height: 42)

            //写真を描画する
            UIGraphicsBeginImageContext(size)

            imageArray[Int(annotation.title!!)!].draw(in: CGRect(x:0, y:0, width : size.width,height: size.height))
            let resizeImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()

            annotationView?.image = resizeImage

            //タップした時に吹き出しが表示されるようにする
            annotationView?.canShowCallout = true
            annotationView?.rightCalloutAccessoryView = UIButton(type: UIButtonType.detailDisclosure)

        } else {
            annotationView!.annotation = annotation
        }
        return annotationView
    }

    //吹き出しをタップした時に呼ばれる
    //画像を表示するように遷移する
    func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
        //画面遷移

        //Annotationを非選択にしてColloutを非表示にする
        mapView.deselectAnnotation(view.annotation, animated: true)
    }
}

//Pickerコントローラー
extension MapViewController:UIImagePickerControllerDelegate{
    //写真を撮り終わった後の処理
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        if let pickerImage = info[UIImagePickerControllerOriginalImage] as? UIImage{
            //画像用の配列に入れる
            imageArray.append(pickerImage)
        }

        self.dismiss(animated: true, completion: nil)

        //最後に取得した位置でCLLocationCoordinate2Dを作成する
        let center : CLLocationCoordinate2D = CLLocationCoordinate2DMake((locationData?.coordinate.latitude)!, (locationData?.coordinate.longitude)!)
        mapView.setCenter(center, animated: true)


        let pin : MKPointAnnotation = MKPointAnnotation()
        pin.coordinate = center
        pin.title = "\(mapView.annotations.count)"
        pin.subtitle = "\(String(describing: locationData?.coordinate.latitude )),\(String(describing: locationData?.coordinate.longitude))"
        mapView.addAnnotation(pin)
    }

    //Pickerイメージでキャンセルされた時
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        self.dismiss(animated: true, completion: nil)
    }
}

//
extension MapViewController:UINavigationControllerDelegate{

}
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 5

+1

解決済みですが、少し気になったことを書かせていただきます。

まず、アノテーションの生成は、pickerImageのif文の中に入れた方がいいと思います。
現在のコードだと、画像の取得に失敗したときにアノテーションと画像の数がズレてしまいます。
(失敗することがあるのかどうかは知りませんが‥)

pin.titleに入れる番号はimageArray.countを使うようにすれば「-1」という謎の補正が不要になります。
ただし、処理順をアノテーション生成 → 画像保存の順に変更しないといけません。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

    self.dismiss(animated: true, completion: nil)

    if let pickerImage = info[UIImagePickerControllerOriginalImage] as? UIImage{

        //まずアノテーションを生成
        //最後に取得した位置でCLLocationCoordinate2Dを作成する
        let center : CLLocationCoordinate2D = CLLocationCoordinate2DMake((locationData?.coordinate.latitude)!, (locationData?.coordinate.longitude)!)
        mapView.setCenter(center, animated: true)

        let pin : MKPointAnnotation = MKPointAnnotation()
        pin.coordinate = center
        pin.title = "\(imageArray.count)" //※修正
        pin.subtitle = "\(String(describing: locationData?.coordinate.latitude )),\(String(describing: locationData?.coordinate.longitude))"
        mapView.addAnnotation(pin)

        //画像用の配列に入れる
        imageArray.append(pickerImage)

    } else {
        print("nil")
    }
}

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

checkベストアンサー

0

imagePickerControllerの中で画像をimageArrayに追加し、併せてMKPointAnnotationを追加しており、画像の数とMKPointAnnotationの数が常に一致することを前提としている、ということでしょうか。
となると、もし両者の不一致があればimageArrayのアクセス時にインデックスの不整合が発生しうると思われます。
imagePickerControllerの当該部分周辺にブレークポイントを仕掛けるなどして、ご確認されるとよさそうです。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/05/18 13:43 編集

    ピンのTitleに振った番号で写真を読み出していたのですが、
    ご指摘のようにimageArrayとのIndexとの不整合が起きていました。
    imageArray[ Int(annotation.title!!)! - 1].draw(in: CGRect(x:0, y:0, width : size.width,height: size.height))
    というように修正しました。

    ただ、PINの画像が表示されないという症状は残ってしまいました
    ただ、質問が分かりづらくなるので 新たな質問でその点はお尋ねしようと思います
    ありがとうございました

    キャンセル

0

・修正するヒント

<当該エラー発生ステップ(行)の特定>
・エラー時にスタックトレースを確認
・ステップ実行するとか、怪しいところにログ出力をさせてみるなどを行い、どの部分で当該エラーが出ているのかを絞り込みます

<エラー発生ステップ内容の確認>
・特定できない場合
特定の仕方が悪い可能性が高い、想定する場所とは違うところでエラーになっている可能性を疑ってみる

・エラーが発生しているステップを特定できたのであれば、そのステップが想定通りの動作をしているのか、変数は想定通りの値になっているのかなどを確認します。

・エラー内容から、配列のインデックス(添え字)の指定した値が、想定より大きい値になっている可能性があるので、その点を重点的に確認するだけで良いかもしれませんが。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

imageArray[ Int(annotation.title!!)! - 1].draw(in: CGRect(x:0, y:0, width : size.width,height: size.height))
と記述を変更したらエラーは出なくなりました。
ただ、写真は表示できていないままです

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/05/18 14:54

    画像を表示するにはMKAnnotationViewを使いますので、
    var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: pinID)

    annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: pinID)
    に変更すれば大丈夫かと。

    キャンセル

  • 2017/05/18 15:13

    画像表示部分関連で気になったのですが、質問者さんご提示のコードですと、アノテーションビューを新規生成した場合は大丈夫そうですが、既存のアノテーションビューを取得した場合にはビューに画像を設定する部分がスキップされてしまうのではないかと思いました。このあたりの挙動はどうなるのでしょう?

    キャンセル

  • 2017/05/18 15:19

    するどいw
    画像もそのまま使い回されてしまうので、おそらく質問者さんの意図した動作にはならないと思います。

    キャンセル

  • 2017/05/18 20:43

    ご指摘ありがとうございます。
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
    この部分のコードを変更して 意図した動きになりました。

    解決方法に修正したコードを載せて起きます

    キャンセル

0

ピンを表示する時の処理を以下のように変更しました。

    //ピンを表示する時の処理
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {

        // 現在地は外す
        if annotation is MKUserLocation {
            return nil
        }

        let pinID = "PIN"
        let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: pinID)

        //var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: pinID) as? MKPinAnnotationView
        let size: CGSize = CGSize(width: 42, height: 42)

        //写真を描画する
        UIGraphicsBeginImageContext(size)
        imageArray[Int(annotation.title!!)!].draw(in: CGRect(x:0, y:0, width : size.width,height: size.height))

        let resizeImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        annotationView.annotation = annotation
        annotationView.image = resizeImage

        //タップした時に吹き出しが表示されるようにする
        annotationView.canShowCallout = true
        //吹き出しにボタンをつける
        annotationView.rightCalloutAccessoryView = UIButton(type: UIButtonType.detailDisclosure)

        return annotationView
    }

同時にPickerControlerの処理を以下のように変更しました

    //写真を撮り終わった後の処理
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        self.dismiss(animated: true, completion: nil)

        //画像が作成できた場合 アノテーション作成 画像保存
        if let pickerImage = info[UIImagePickerControllerOriginalImage] as? UIImage{
            //アノテーション作成
            //最後に取得した位置でCLLocationCoordinate2Dを作成する
            let center : CLLocationCoordinate2D = CLLocationCoordinate2DMake((locationData?.coordinate.latitude)!, (locationData?.coordinate.longitude)!)
            mapView.setCenter(center, animated: true)               //地図のセンターに設定する
            let pin : MKPointAnnotation = MKPointAnnotation()       //ピンを作成する
            pin.coordinate = center                                 //最後に取得した位置情報を設定する
            pin.title = "\(imageArray.count)"

            pin.subtitle = "\(String(describing: locationData?.coordinate.latitude )),\(String(describing: locationData?.coordinate.longitude))"
            //地図にピンを立てる
            mapView.addAnnotation(pin)
            //画像用の配列に入れる
            imageArray.append(pickerImage)
        }else{
            print("画像作成に失敗した")
        }

    }

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

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