

### 前提・実現したいこと

地図的なアプリを作っているのですが、ピンの画像を、FirebaseStorageに格納している個別の画像に変更したいです。

コードの前半部分は関係ないと思いますが、初心者でどこまでお見せすればよいかわからないため全体を公開します。

### 発生している問題・エラーメッセージ

ピンの画像がデフォルトのまま変わりません。  
画像の取得自体はうまくいっているようで、`print(imageimage!)`では格納している画像についての情報を読めるのですが、実際のピンはデフォルトのままです。

また、ビルドエラーで一番下の}で

[code]

    Missing return in a function expected to return 'MKAnnotationView!'
    
[/code]

とエラーメッセージが出てきます。  
`return`はしているつもりなのですが、どこか間違っているのでしょうか？

### 該当のソースコード

[code]

    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
        if annotation === mapView.userLocation { // 現在地を示すアノテーションの場合はデフォルトのまま
            return nil
        } else {
            //配列の該当のデータをitemという定数に代入?
            let items = contentArray
            //画像を取ってくる、まずはFirebaseStorageに接続
            let storage = Storage.storage()
    
            // Create a storage reference from our storage service
            let storageRef = storage.reference(forURL: "https://firebasestorage.googleapis.com/v0/b/catmap-44f0a.appspot.com/o/")
    
            for item in items {
                //itemの中身を辞書型に変換?
                let content = item.value as! Dictionary <String, Any>
                for (key, element) in content{
                    let pinData = content[key] as! [String: Any]
                    if let geohash = pinData["geohash"] as? String {
    
                        let (latitude, longitude) = Geohash.decode(hash: geohash)!
                        let annotation = MKPointAnnotation()
                        annotation.coordinate = CLLocationCoordinate2DMake(CLLocationDegrees(latitude.min), CLLocationDegrees(longitude.min))
                        annotationArray.append(annotation)
    
                        let reuseId = "pin"
                        if let pinView = self.mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) {
                            return pinView
                        }
                        else { // 再利用できるアノテーションが無い場合（初回など）は生成する
                            let pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
    
                            // Firebase Storageの該当imageのReference
                            let catRef = storageRef.child("images/\(key)/0.jpg")
    
                            // Fetch the download URL
                            catRef.downloadURL { (URL, error) -> Void in
                                if (error != nil) {
                                    // Handle any errors
                                } else {
                                    // Get the download URL for 'images/stars.jpg'
                                    // URLを指定したUIImageの生成例
                                    let PictureURL = URL!
    
                                    /*
                                     デフォルト設定でセッションオブジェクトを作成する。
                                      */
                                    let session = URLSession(configuration: .default)
                                    /*
                                     ダウンロードタスクを定義します。ダウンロードタスクは、
                                     URLの内容をデータオブジェクトとしてダウンロードし、
                                     そのデータで望むことを実行できます。
                                     */
                                    let downloadPicTask = session.dataTask(with: catPictureURL) { (data, response, error) in
                                        /*
                                         ダウンロードが完了しました。
                                         */
    
                                        if let e = error {
                                            print("cat pictureのダウンロード中にエラーが発生しました: \(e)")
                                        } else {
                                            /*
                                             エラーは見つかりませんでした。
                                             レスポンスがないと変わってしまいますので、それもチェックしてください。
                                             */
                                            if let res = response as? HTTPURLResponse {
                                                print("\(res.statusCode)")
                                                if let imageData = data {
                                                    /*
                                                     最後に、そのデータをイメージに変換し、
                                                     それを使って望むことをします。
                                                     */
    
                                                    let imageimage = UIImage(data: imageData)
                                                    print(imageimage!)
                                                    pinView.image = imageimage
                                                    pinView.annotation = annotation
    
                                                } else {
                                                    print("画像を取得できませんでした：画像はありません")
                                                }
                                            } else {
                                                print("何らかの理由で応答コードを取得できませんでした")
                                            }
                                        }
                                    }
                                    downloadPicTask.resume()
                                }
                            }
                            return pinView
                        }
                        self.mapView.addAnnotations(annotationArray)
                    }
                }
            }
        }
    }
    
[/code]

### 補足情報(言語/FW/ツール等のバージョンなど)

  * swift3
  * xcode8.3.3


CC BY-SA 3.0
2017-07-06T00:50:17.500
36122
2020-09-25T11:03:40.467
2017-07-06T01:06:01.253
21092
24242
question
swift ios xcode swift3 xcode8
地図上のピンの画像を、外部ストレージに格納している個別の画像に変更したい


コメントに示したようにどこからどこまでをどんな方法で動作確認されたのかが全くわかりませんので、具体的に動作確認ができる形でのコードをお示しすることができないのですが、ざっと見ただけでも大幅に修正しなければ動くようにはできないので、概要だけでもお伝えしておきます。

* * *

あなたのコードの大枠を示すと以下のようになっています。

[code]

    // ↓(1)
    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
        if annotation === mapView.userLocation { // 現在地を示すアノテーションの場合はデフォルトのまま
            return nil
        } else {
            //配列の該当のデータをitemという定数に代入?
            let items = contentArray
            //...
    
            for item in items {
                //itemの中身を辞書型に変換?
                let content = item.value as! Dictionary <String, Any>
                for (key, element) in content{
                    let pinData = content[key] as! [String: Any]
                    if let geohash = pinData["geohash"] as? String {
    
                        let (latitude, longitude) = Geohash.decode(hash: geohash)!
                        let annotation = MKPointAnnotation() //-> (4)
                        annotation.coordinate = CLLocationCoordinate2DMake(CLLocationDegrees(latitude.min), CLLocationDegrees(longitude.min))
                        annotationArray.append(annotation) //-> (5)
    
                        let reuseId = "pin"
                        if let pinView = self.mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) {
                            return pinView //-> (2)
                        }
                        else { // 再利用できるアノテーションが無い場合（初回など）は生成する
                            let pinView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
    
                            //...
    
                            return pinView //-> (2)
                        }
                        self.mapView.addAnnotations(annotationArray) //-> (3)
                    }
                }
            }
        }
    }
    
[/code]

(1) お示しのメソッドヘッダーはSwift 2までのものです。Swift
3では、同じ`MKMapViewDelegate`のメソッドは次のようになります。

[code]

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        //...
    }
    
[/code]

Swiftはまだ発展途上の言語で、とりわけSwift
2→3では、ほぼ全ての実用的なアプリで大幅に修正が必要な変更が加えられました。ネット上から参考コードを引っ張ってくるような場合には、記事内容がご自身の使っているSwiftと同バージョンかどうかを確認しないと、SwiftやiOSのプログラミングよりも、Swiftの言語変遷史を学習する羽目になってしまいます。お気をつけください。

(2) あなたがこのメソッドに対する`return`文を書いているのは、(`annotation ===
mapView.userLocation`が成立する場合の`nil`を除けば)`if let geohash = pinData["geohash"]
as?
String`の条件が成立する場合だけになっています。その条件を満たすデータがひとつも`contentArray`の中に存在しない場合に実行されるような`return`文がありません。そのために`Missing
return`のエラーが発生しています。

(3)
通常`mapView(_:viewFor:)`メソッドの中で、`MKMapView.addAnnotations(_:)`を呼ぶことはあり得ません。`addAnnotations(_:)`はどこか別の場所で、地図が表示される前に実行しておいて、`mapView(_:viewFor:)`メソッドでは単に`annotation`パラメーターで示されるアノテーション用の`MKAnnotationView`を返します。

(4)
上記しましたが、あなたのコードにあるように`annotation`パラメーターを無視してしまって別の`MKAnnotation`インスタンス(`MKPointAnnotation`)を`mapView(_:viewFor:)`メソッド内で作成するなんてことは通常あり得ません。

(5)
`annotationArray`の宣言がメソッド内には見当たりませんから、これはインスタンスプロパティなのでしょうか。この配列に対する`append(_:)`は行なっていますが、配列の中身を空にするコードは見当たりません。と言うことは、`mapView(_:viewFor:)`メソッドが呼ばれるたび(`MKMapView`の表示中はかなり頻繁に呼び出されます)に配列の内容が膨れ上がっていくことになります。

* * *

ざっくりと修正の方針を示すと以下のようになります。

  * 「`contentArray`からアノテーションを作成して`addAnnotations(_:)`を行う処理」は、`mapView(_:viewFor:)`の他のメソッド(`viewDidLoad()`かアノテーションが確定するようなアクションメソッド、当然ながら同じアノテーションを何度も`addAnnotations(_:)`してはいけない)の中に移動させる。(この時点で作成するのは`MKAnnotation`の実装クラスであり、`MKAnnotationView`ではない。)

  * `mapView(_:viewFor:)`では、`annotation`パラメーターを元に作成した`MKMapAnnotationView`を一つ返す。そのために必要なプロパティが既存の`MKAnnotation`クラスに存在しないのであれば、オリジナルの`MKAnnotation`クラスを作成する。

大幅修正が必要となるでしょうから、上記の方針を元にコンパイルが通る程度のコードにまで修正できたら、まだ所望の動作と異なる部分が出たとしても個別に別質問としていただいた方が良いでしょう。


CC BY-SA 3.0
2017-07-06T05:49:21.157
36138
2017-07-06T05:49:21.157
13972
36122
answer