Swift - CLGeocoder反向地理编码完成处理程序闭包

23

我试图将一个CLLocation传递给函数getPlacemarkFromLocation,然后通过reverseGeocodeLocation使用传递的CLLocation来设置将返回的CLPlacemark?

我在reverseGeocodeLocation中创建completionHandler闭包时遇到了问题,它会引发编译器错误/崩溃:


在Swift中,根据文档,CLGeocodeCompletionHandler = (AnyObject[]!, NSError!) -> VoidAnyObject[]!应该包含像Objective-C版本一样的CLPlacemark对象。

这是我的当前代码:

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?
}

编辑:看起来错误与placemarks.count有关,因为placemarks没有像一个数组一样进行处理。现在它可以编译了,但是在尝试在completionHandler中设置p时,我得到了除了nil之外的任何东西。我已经检查了传递的CLLocation,它们是有效的。

编辑2:在打印placemarks后,我可以确认它返回数据。但是p仍然返回nil。

6个回答

25

我在这个帖子中找到了所需的答案:Set address string with reverseGeocodeLocation: and return from method

问题在于reverseGeocodeLocation是异步执行的,在我的示例中,在completionBlock设置p之前,该方法就已经返回了一个值。


按要求,这是我的当前代码。

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) }
    })
}

我不想采用NSNotificationCenter路线,因为那样会增加不必要的开销,相反,在completionHandler闭包中,我调用另一个函数并将由getPlacemarkFromLocation生成的CLPlacemark作为参数传递,以保持异步处理,因为该函数将在设置placemarks后被调用,函数(应)接收所需的地标并执行您想要的代码。希望我说的有意义。


你能详细说明一下你是如何解决这个问题的吗?我已经尝试了外部函数和通知,但仍然收到EXC_BAD_ACCESS错误。提前感谢,这将有助于其他转向Swift的开发人员。 - pxpgraphics
抱歉,我之前使用了这个语法... self!.geocoder.reverseGeocodeLocation(location, completionHandler:{ [weak self] (placemarks: [AnyObject]!, error: NSError!) -> Void in // 在这里编写你的代码。 }) - pxpgraphics
当然,我会编辑这篇文章并添加我的当前代码,并详细说明一下。 - AaronDancer

15

使用这些Swift代码,您可以完整打印出位置的地址:

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)
        }
    })
}

干杯!


3

以下是我用过的可以解决问题的闭包,但是要花点时间才能让它正常运作。我认为你的问题与未使用正确的初始化程序来初始化 "p" 变量有关。我尝试了几个不同的方式,直到我发现这个方法可行:self.placemark = CLPlacemark(placemark: stuff[0] as 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
        }

        })

编辑:

将更好的答案移动到其自己的答案中。


似乎 p 没有被初始化是问题所在,因此我将 p = placemarks[0] as? CLPlacemark 更改为 p = CLPlacemark(placemark: placemarks[0] as? CLPlacemark),但是 p 仍然返回 nil。我尝试在调用 reverseGeocodeLocation 之前初始化 p,但是当尝试更改值时出现 EXC_BAD_ACCESS。我无法事先使用 CLPlacemark(placemark: CLPlacemark?) 初始化程序,因为我没有 CLPlacemark 可以提供。 - AaronDancer

0

对于异步编程,你可能需要从头开始了解一些基础知识。虽然有点晚了,但你现在应该已经学会了。

你的代码基本问题在于,p(你的标记)是在函数返回后设置的,因此它被丢失了——你不能使用函数通过异步来返回值。使用完成闭包时,当 placemark 异步到达并调用该闭包时,你的代码将传递 placemark,注意现在该函数不再返回任何内容。

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)
    })
}

使用 -

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

我还没有把这个放到Xcode中,但是看起来没问题...


0

编辑:这个不起作用。在闭包外面,该值为nil--请参见下面的评论

您的p是nil,因为闭包在其初始化为引用之前就捕获了它。要获得所需的行为,您需要将p设置为非可选值,例如var p:CLPlacemark!。

以下是我用来测试我的猜测的代码:

 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)")

}

这是控制台日志:

Pushit <- 按钮按下以开始位置捕获
p 中的外部内容:空
p 中的内部内容:美国
p 中的外部内容:空
p 中的内部内容:美国
p 中的外部内容:空...


我也尝试过那样做,但是这样做时我会得到EXC_BAD_ACCESS错误。 - AaronDancer
我已经再次尝试了,当使用非可选值时,我得到了与您上面示例相同的结果。 - AaronDancer
Swift中的闭包似乎不像我期望的那样工作。唯一能让内部值在闭包外“保持”的方法是,如果placemark是控制器的成员变量。如果Swift支持静态/类变量(显然在beta版中不可用),它可能会起作用。请注意,我甚至尝试使用在闭包外初始化的本地数组。我在闭包中将placemark添加(附加)到数组中。但是在闭包外部,该数组仍然为空。 - JohnInSea
Aaron,你下面的解决方案非常有道理。干得好。玩弄这个东西很有趣,我学到了很多关于Swift闭包的知识。如果我有权限的话,我会在相关答案中发表评论... :) - JohnInSea
从Beta 3开始,数组类型现在使用元素类型周围的方括号来编写...更改: let pm = placemarks as? CLPlacemark[] 为: let pm = placemarks as? [CLPlacemark] - pxpgraphics
是的。昨天注意到了。非常奇怪。我会进行编辑。 - AaronDancer

-4

你的东西出了很多问题。这里是我修复的部分,没有实际查看功能:

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

这仍将返回nil - 你没有意识到p是在你返回值之后设置的,因为reverseGeocodeLocation是异步工作的。 - SomaMan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接