web-dev-qa-db-fra.com

Swift - CLGeocoder reverseGeocodeLocation completionHandler fermeture

Ce que j'essaie de faire est de passer un CLLocation à la fonction getPlacemarkFromLocation qui utilise ensuite le CLLocation passé à reverseGeocodeLocation pour définir le CLPlacemark? qui sera renvoyé. 

J'ai des problèmes pour créer la fermeture completionHandler dans reverseGeocodeLocationil génère une erreur/crash du compilateur:


Dans Swift, CLGeocodeCompletionHandler est CLGeocodeCompletionHandler = (AnyObject[]!, NSError!) -> Void selon la documentation. AnyObject[]! est censé contenir des objets CLPlacemark, tout comme la version d'Objective-C.

Voici mon code actuel:

class func getPlacemarkFromLocation(location:CLLocation)->CLPlacemark?{
    var g = CLGeocoder()
    var p:CLPlacemark?
    g.reverseGeocodeLocation(location, completionHandler: {
        (placemarks, error) in
        let pm = placemarks as? CLPlacemark[]
        if (pm && pm?.count > 0){
            p = placemarks[0] as? CLPlacemark
        }
    })
    return p?
}

EDIT: Il semble que l’erreur concerne placemarks.count avec placemarks ne pas être traité comme un tableau. Il compile maintenant, cependant je n’obtiens rien mais nil lorsque j'essaie de placer p dans le completionHandler. J'ai vérifié le CLLocations étant passé et ils sont valides.

EDIT 2: Après avoir imprimé placemarks, je peux confirmer que les données sont renvoyées. Cependant, p renvoie toujours zéro.

22
AaronDancer

J'ai trouvé la réponse dont j'avais besoin dans ce fil de discussion: Définir la chaîne d'adresse avec reverseGeocodeLocation: et renvoyer depuis la méthode

Le problème réside dans le fait que reverseGeocodeLocation est asynchrone, la méthode renvoie une valeur avant que0000LachidationBlock définit p dans mon exemple.


Comme demandé, voici mon code actuel.

func showAddViewController(placemark:CLPlacemark){
    self.performSegueWithIdentifier("add", sender: placemark) 
}

func getPlacemarkFromLocation(location: CLLocation){
    CLGeocoder().reverseGeocodeLocation(location, completionHandler:
        {(placemarks, error) in
            if error {println("reverse geodcode fail: \(error.localizedDescription)")}
            let pm = placemarks as [CLPlacemark]
            if pm.count > 0 { self.showAddPinViewController(placemarks[0] as CLPlacemark) }
    })
}

Je ne voulais pas prendre la route NSNotificationCenter car cela ajouterait une surcharge inutile. Plutôt dans la fermeture completionHandler, j'appelle une autre fonction et transmets la CLPlacemark générée par getPlacemarkFromLocation en tant que paramètre pour que les choses restent asynchrones car la fonction sera appelée après placemarks définir la fonction (devrait) recevoir le repère nécessaire et exécuter le code souhaité. J'espère que ce que j'ai dit est logique.

23
AaronDancer

Avec ces lignes de Swift, vous pouvez imprimer intégralement l'adresse du lieu:

func getLocationAddress(location:CLLocation) {
    var geocoder = CLGeocoder()

    println("-> Finding user address...")

    geocoder.reverseGeocodeLocation(location, completionHandler: {(placemarks, error)->Void in
        var placemark:CLPlacemark!

        if error == nil && placemarks.count > 0 {
            placemark = placemarks[0] as CLPlacemark

            var addressString : String = ""
            if placemark.ISOcountryCode == "TW" /*Address Format in Chinese*/ {
                if placemark.country != nil {
                    addressString = placemark.country
                }
                if placemark.subAdministrativeArea != nil {
                    addressString = addressString + placemark.subAdministrativeArea + ", "
                }
                if placemark.postalCode != nil {
                    addressString = addressString + placemark.postalCode + " "
                }
                if placemark.locality != nil {
                    addressString = addressString + placemark.locality
                }
                if placemark.thoroughfare != nil {
                    addressString = addressString + placemark.thoroughfare
                }
                if placemark.subThoroughfare != nil {
                    addressString = addressString + placemark.subThoroughfare
                }
            } else {
                if placemark.subThoroughfare != nil {
                    addressString = placemark.subThoroughfare + " "
                }
                if placemark.thoroughfare != nil {
                    addressString = addressString + placemark.thoroughfare + ", "
                }
                if placemark.postalCode != nil {
                    addressString = addressString + placemark.postalCode + " "
                }
                if placemark.locality != nil {
                    addressString = addressString + placemark.locality + ", "
                }
                if placemark.administrativeArea != nil {
                    addressString = addressString + placemark.administrativeArea + " "
                }
                if placemark.country != nil {
                    addressString = addressString + placemark.country
                }
            }

            println(addressString)
        }
    })
}

À votre santé!

15
Goon Nguyen

Voici la fermeture qui a fonctionné pour moi - il a fallu un certain temps pour que cela fonctionne. Je pense que votre problème est lié au fait de ne pas initialiser p avec le bon initialiseur. J'ai essayé quelques variantes jusqu'à ce que cela fonctionne: self.placemark = CLPlacemark (repère: stuff [0] comme CLPlacemark)

geocoder.reverseGeocodeLocation(newLocation, completionHandler: {(stuff, error)->Void in

        if error {
            println("reverse geodcode fail: \(error.localizedDescription)")
            return
        }

        if stuff.count > 0 {
            self.placemark = CLPlacemark(placemark: stuff[0] as CLPlacemark)

            self.addressLabel.text = String(format:"%@ %@\n%@ %@ %@\n%@",
                self.placemark.subThoroughfare ? self.placemark.subThoroughfare : "" ,
                self.placemark.thoroughfare ? self.placemark.thoroughfare : "",
                self.placemark.locality ? self.placemark.locality : "",
                self.placemark.postalCode ? self.placemark.postalCode : "",
                self.placemark.administrativeArea ? self.placemark.administrativeArea : "",
                self.placemark.country ? self.placemark.country : "")
        }
        else {
            println("No Placemarks!")
            return
        }

        })

MODIFIER:

déplacé meilleure réponse à sa propre réponse.

3
JohnInSea

Un peu en retard pour cette soirée, mais il semble que vous ayez besoin de faire des lectures approfondies sur le contenu asynchrone. En disant cela, vous l'avez probablement déjà appris.

Le problème fondamental de votre code est que p (votre repère) est défini après le retour de la fonction. Il est donc tout simplement perdu. Vous ne pouvez pas utiliser une fonction pour renvoyer une valeur asynchrone. Avec une fermeture complète, votre code se voit attribuer le repère dès son arrivée (de manière asynchrone) et la fermeture est invoquée - notez que la fonction ne renvoie plus rien.

func getPlacemarkFromLocation(_ location: CLLocation, completion: ((CLPlacemark?) -> ())) {

    CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) in
        // use optional chaining to safely return a value or nil
        // also using .first rather than checking the count & getting placemarks[0] -
        // if the count is zero, will just give you nil
        // probably a good idea to check for errors too
        completion(placemarks?.first)
    })
}

Utilisation -

getPlacemarkFromLocation(myLocation, completion: { (placemark) in
    // do something with the placemark here
})

En fait, je n'ai pas mis cela dans Xcode, mais ça a l'air bien ...

0
SomaMan

EDIT: Cela ne fonctionne pas. La valeur est nulle en dehors de la fermeture - voir les commentaires ci-dessous

Votre p est nul parce que la fermeture le capture avant qu'il ne soit initialisé à une référence. Pour obtenir le comportement souhaité, vous devez définir p comme valeur non optionnelle telle que var p: CLPlacemark !.

Ci-dessous le code que j'ai utilisé pour tester ma conjecture:

 func locationManager(manager: CLLocationManager!, didUpdateToLocation newLocation: CLLocation!, fromLocation oldLocation: CLLocation!) {
    var g = CLGeocoder()
    var p:CLPlacemark?
    let mynil = "empty"
    g.reverseGeocodeLocation(newLocation, completionHandler: {
        (placemarks, error) in
        let pm = placemarks as? CLPlacemark[]
        if (pm && pm?.count > 0){
           // p = CLPlacemark()
            p = CLPlacemark(placemark: pm?[0] as CLPlacemark)

            println("Inside what is in p: \(p?.country ? p?.country : mynil)")

        }

        })

    println("Outside what is in p: \(p?.country ? p?.country : mynil)")

}

Voici le journal de la console:

Pushit <- bouton enfoncé pour lancer la capture de la position
En dehors de ce qui est dans p: vide
À l'intérieur de ce qui est dans p: États-Unis
En dehors de ce qui est dans p: vide
À l'intérieur de ce qui est dans p: États-Unis
En dehors de ce qui est dans p: vide ...

0
JohnInSea