正确使用 MKLocalSearch CompletionHandler

Posted

技术标签:

【中文标题】正确使用 MKLocalSearch CompletionHandler【英文标题】:Using MKLocalSearch CompletionHandler correctly 【发布时间】:2018-02-08 02:02:20 【问题描述】:

我正在使用 mapkit 制作一个应用程序,帮助用户找到附近吸引他们的餐厅(以及其他东西),并尝试使用 MKLocalSearch。我已经声明了 myMapItems,它是一个 MKMapItem 数组,并试图将它设置为等于我的 MKLocalSearch 的结果。当打印我的 MKLocalSearch 的结果时,我得到了所有 10 个 MKMapItems,但是当将 myMapItems 数组设置为等于结果时,控制台告诉我 myMapItems 为 nil。所以: var myMapItems: [MKMapItem]?

然后,在将“区域”定义为 MKCoordinateRegion 之后

let request = MKLocalSearchRequest()
    request.naturalLanguageQuery = "Restaurants"
    request.region = region!
    let search = MKLocalSearch(request: request)
    search.start  (response, error) in
        print(response?.mapItems)
        self.myMapItems = response?.mapItems
    

因此,当我按下运行此代码的按钮时,控制台会打印 response.mapItems,但未能将我之前声明的 mapItems 设置为等于搜索结果。

更详细的代码:

import UIKit
import MapKit

class MapViewController: UIViewController, CLLocationManagerDelegate 
@IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()

@IBOutlet weak var slider: UISlider!

@IBAction func searchButtonPressed(_ sender: Any) 
    search()
    print(self.mapItems)


var mapItems: [MKMapItem] = []

func search() 

    var region: MKCoordinateRegion?
    let userLocation = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)

    //Function for translating a CLLocationCoordinate2D by x meters
    func locationWithBearing(bearing:Double, distanceMeters:Double, origin:CLLocationCoordinate2D) -> CLLocationCoordinate2D 
        let distRadians = distanceMeters / (6372797.6)

        let rbearing = bearing * Double.pi / 180.0

        let lat1 = origin.latitude * Double.pi / 180
        let lon1 = origin.longitude * Double.pi / 180

        let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(rbearing))
        let lon2 = lon1 + atan2(sin(rbearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))

        return CLLocationCoordinate2D(latitude: lat2 * 180 / Double.pi, longitude: lon2 * 180 / Double.pi)
    

    //Function for generating the search region within user's specified radius
    func generateRandomSearchRegionWithinSpecifiedRadius() 
        //Get user location
        let userCurrentLocation = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude)

        //Set randomLocationWithinSearchRadius to the user's current location translated in a randomly selected direction by a distance within x miles as specified by the user's slider
        let randomLocationWithinSearchRadius = locationWithBearing(bearing: Double(arc4random_uniform(360)), distanceMeters: Double(arc4random_uniform(UInt32(slider.value * 1609.34))), origin: userCurrentLocation)

        //Set region to an MKCoordinateRegion with this new CLLocationCoordinate2D as the center, searching within 3 miles
        region = MKCoordinateRegionMakeWithDistance(randomLocationWithinSearchRadius, 4828.03, 4828.03)
        print("\nRegion:\n     Lat: \(region?.center.latitude ?? 0)\n     Long: \(region?.center.longitude ?? 0)\n     Radius: \(round((region?.span.latitudeDelta)! * 69))")
    

    //Generate the random searching region within specified radius
    generateRandomSearchRegionWithinSpecifiedRadius()

    //Find distance between userLocation and our generated search location
    let distanceBetweenLocations = CLLocation(latitude: (region?.center.latitude)!, longitude: (region?.center.longitude)!).distance(from: CLLocation(latitude: (userLocation.coordinate.latitude), longitude: (userLocation.coordinate.longitude)))

    //Compare distance between locations with that requested by user's slider
    print("\n     Distance between locations: \(distanceBetweenLocations / 1609.34)\n     Slider value: \(slider.value)\n\n\n")

    //If the function generates a location whose distance from the user is further than that requested by the slider, the function will repeat
    while distanceBetweenLocations > Double((slider.value) * 1609.34) 
        generateRandomSearchRegionWithinSpecifiedRadius()
    

    let request = MKLocalSearchRequest()
    request.naturalLanguageQuery = "Restaurants"
    request.region = region!
    let search = MKLocalSearch(request: request)
    search.start  (response, error) in
        //print(response?.mapItems)
        for item in (response?.mapItems)! 
            self.mapItems.append(item)
        
    

整个班级都应该有人需要它:

import UIKit
import MapKit

class MapViewController: UIViewController, CLLocationManagerDelegate 
@IBOutlet weak var mapView: MKMapView!
let locationManager = CLLocationManager()

@IBOutlet weak var pickerTextField: UITextField!
let actions = ["A place to eat", "Something fun to do", "A park", "A club or bar"]

@IBOutlet weak var slider: UISlider!

@IBOutlet weak var distanceLabel: UILabel!

@IBOutlet weak var searchButton: UIButton!

override func viewDidLoad() 
    super.viewDidLoad()

    pickerTextField.loadDropdownData(data: actions)
    // Do any additional setup after loading the view, typically from a nib.

    // Core Location Manager asks for GPS location
    locationManager.delegate = self
    locationManager.desiredAccuracy = kCLLocationAccuracyBest
    locationManager.requestWhenInUseAuthorization()
    locationManager.startMonitoringSignificantLocationChanges()

    // Check if the user allowed authorization
    if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
        CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways)
    
        // set initial location as user's location
        let initialLocation = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
        centerMapOnLocation(location: initialLocation, regionRadius: 1000)
        print("Latitude: \(locationManager.location?.coordinate.latitude), Longitude: \(locationManager.location?.coordinate.longitude)")
     else 
        print("Failed")
    

    let span : MKCoordinateSpan = MKCoordinateSpanMake(0.1, 0.1)
    let location : CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 38.645933, longitude: -90.503613)

    let pin = PinAnnotation(title: "Gander", subtitle: "You can get food here", coordinate: location)
    mapView.addAnnotation(pin)


override func didReceiveMemoryWarning() 
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.



func centerMapOnLocation(location: CLLocation, regionRadius: Double) 
    let regionRadius = CLLocationDistance(regionRadius)

    let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate,
                                                              regionRadius, regionRadius)
    mapView.setRegion(coordinateRegion, animated: true)


@IBAction func sliderAdjusted(_ sender: Any) 
    var int = Int(slider.value)
    distanceLabel.text = "\(int) miles"


@IBAction func searchButtonPressed(_ sender: Any) 
    search()
    print(self.mapItems)
    //chooseRandomSearchResult(results: self.mapItems!)



var mapItems: [MKMapItem] = []

func search() 

    var region: MKCoordinateRegion?
    let userLocation = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)

    //Function for translating a CLLocationCoordinate2D by x meters
    func locationWithBearing(bearing:Double, distanceMeters:Double, origin:CLLocationCoordinate2D) -> CLLocationCoordinate2D 
        let distRadians = distanceMeters / (6372797.6)

        let rbearing = bearing * Double.pi / 180.0

        let lat1 = origin.latitude * Double.pi / 180
        let lon1 = origin.longitude * Double.pi / 180

        let lat2 = asin(sin(lat1) * cos(distRadians) + cos(lat1) * sin(distRadians) * cos(rbearing))
        let lon2 = lon1 + atan2(sin(rbearing) * sin(distRadians) * cos(lat1), cos(distRadians) - sin(lat1) * sin(lat2))

        return CLLocationCoordinate2D(latitude: lat2 * 180 / Double.pi, longitude: lon2 * 180 / Double.pi)
    

    //Function for generating the search region within user's specified radius
    func generateRandomSearchRegionWithinSpecifiedRadius() 
        //Get user location
        let userCurrentLocation = CLLocationCoordinate2DMake(userLocation.coordinate.latitude, userLocation.coordinate.longitude)

        //Set randomLocationWithinSearchRadius to the user's current location translated in a randomly selected direction by a distance within x miles as specified by the user's slider
        let randomLocationWithinSearchRadius = locationWithBearing(bearing: Double(arc4random_uniform(360)), distanceMeters: Double(arc4random_uniform(UInt32(slider.value * 1609.34))), origin: userCurrentLocation)

        //Set region to an MKCoordinateRegion with this new CLLocationCoordinate2D as the center, searching within 3 miles
        region = MKCoordinateRegionMakeWithDistance(randomLocationWithinSearchRadius, 4828.03, 4828.03)
        print("\nRegion:\n     Lat: \(region?.center.latitude ?? 0)\n     Long: \(region?.center.longitude ?? 0)\n     Radius: \(round((region?.span.latitudeDelta)! * 69))")
    

    //Generate the random searching region within specified radius
    generateRandomSearchRegionWithinSpecifiedRadius()

    //Find distance between userLocation and our generated search location
    let distanceBetweenLocations = CLLocation(latitude: (region?.center.latitude)!, longitude: (region?.center.longitude)!).distance(from: CLLocation(latitude: (userLocation.coordinate.latitude), longitude: (userLocation.coordinate.longitude)))

    //Compare distance between locations with that requested by user's slider
    print("\n     Distance between locations: \(distanceBetweenLocations / 1609.34)\n     Slider value: \(slider.value)\n\n\n")

    //If the function generates a location whose distance from the user is further than that requested by the slider, the function will repeat
    while distanceBetweenLocations > Double((slider.value) * 1609.34) 
        generateRandomSearchRegionWithinSpecifiedRadius()
    

    let request = MKLocalSearchRequest()
    request.naturalLanguageQuery = "Restaurants"
    request.region = region!
    let search = MKLocalSearch(request: request)
    search.start  (response, error) in
        //print(response?.mapItems)
        for item in (response?.mapItems)! 
            self.mapItems.append(item)
        
    

func chooseRandomSearchResult(results: [MKMapItem]) -> MKMapItem 
    let numberOfItems = results.count
    let randomNumber = Int(arc4random_uniform(UInt32(numberOfItems)))
    return results[randomNumber]

【问题讨论】:

您在控制台的哪一行打印出来? @MitchellCurrie 在 search.start 中,当我按下按钮时会在函数中调用它。我也将 self.myMapItems 设置为打印的相同结果,但这样做之后我让它打印 myMapItems 并且控制台说它为零。每当我尝试使用 myMapItems 的内容时,应用程序也会崩溃,因为它被设置为 nil。 你能发布更多代码,包括类和任何实现的委托方法 @MitchellCurrie 我添加了大部分课程,删除了我认为与问题无关的大部分材料。按下我的 UIButton 时会调用 search() 函数,而 search() 开头的大部分代码只是通过用户给定的变量生成“区域”(在搜索请求中使用)。跨度> 奇怪的是你说当你尝试使用 mapItems 时它会崩溃,但它是一个可选的,我没有看到你在任何地方强制展开。不管怎样,更安全的选择是始终使用 EMPTY 数组。 【参考方案1】:

问题是 search.start 是异步的,它将启动请求并在结果完成之前返回。假设您需要在完成处理程序中工作。 替换:

@IBAction func searchButtonPressed(_ sender: Any) 
    search()
    print(self.mapItems)
    //chooseRandomSearchResult(results: self.mapItems!)


到:(删除结果的使用,因为它们还没有,实际上搜索已被触发但尚未返回)

 @IBAction func searchButtonPressed(_ sender: Any) 
        search()
    

并在回调中做实际工作:

//Still inside search()
search.start  (response, error) in
    //Executed after search() has already returned
    print(response?.mapItems)
    self.mapItems = response?.mapItems
    chooseRandomSearchResult(results: self.mapItems!)

//Still inside search()

如您所见,代码标记为: //在search()已经返回后执行 总是在 //Still inside search() 之后执行 即使它在函数之前

(从 ios 11.x 开始,文档保证 MKLocalSearch.start 的完成处理程序将在主线程上)

【讨论】:

在 search.start 中调用 chooseRandomSearchResult(results: self.mapItems!) 而不是 searchPressed() 似乎可以解决问题。非常感谢! 很好,记住闭包可以是异步的

以上是关于正确使用 MKLocalSearch CompletionHandler的主要内容,如果未能解决你的问题,请参考以下文章

使用 MKLocalSearch/MapKit 查找附近的餐馆?

为啥使用 MKLocalSearch 会返回错误并且无法加载预期结果?

如何使用 MKLocalSearch 请求获取附近的地方、照片

在 Swift 中使用 MKLocalSearch 将 pin 附加到用户位置而不是蓝点

iOS MKLocalSearch导致崩溃[重复]

MKLocalSearch 返回区域外的结果