简体   繁体   English

正确使用 MKLocalSearch CompletionHandler

[英]Using MKLocalSearch CompletionHandler correctly

I am using mapkit to make an app that helps users find nearby restaurants (among other things) that appeal to them and am trying to utilize a MKLocalSearch.我正在使用 mapkit 制作一个应用程序,帮助用户找到附近吸引他们的餐馆(除其他外),并尝试使用 MKLocalSearch。 I have declared myMapItems, which is an array of MKMapItem, and am trying to set it equal to the results of my MKLocalSearch.我已经声明了 myMapItems,它是一个 MKMapItem 数组,并试图将它设置为等于我的 MKLocalSearch 的结果。 When printing the results of my MKLocalSearch, I am provided with all ten MKMapItems, but when setting the myMapItems array equal to the results, the console is telling me that myMapItems is nil.在打印 MKLocalSearch 的结果时,我得到了所有十个 MKMapItems,但是当将 myMapItems 数组设置为等于结果时,控制台告诉我 myMapItems 为零。 So: var myMapItems: [MKMapItem]?所以: var myMapItems: [MKMapItem]?

and then later, after defining "region" as an MKCoordinateRegion然后在将“区域”定义为 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
    }

So when I press the button that runs this code, the console prints response.mapItems , but fails to set my mapItems declared earlier equal to the search's results.因此,当我按下运行此代码的按钮时,控制台会打印response.mapItems ,但无法将之前声明的 mapItems 设置为等于搜索结果。

More detailed code:更详细的代码:

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

The entire class should anybody need it:整个班级应该有人需要它:

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

} }

The problem would be that search.start is ASYNCHRONOUS, it will start the request and return before results are done.问题是 search.start 是 ASYNCHRONOUS,它将启动请求并在结果完成之前返回。 Assume you need to work in completion handler.假设您需要在完成处理程序中工作。 Replace:代替:

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

}

To: (Remove the usage of the results as they are not there yet, literally the search has been fired but has not returned)至:(删除结果的使用,因为它们尚不存在,实际上搜索已被触发但尚未返回)

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

AND do the actual work in the callback:并在回调中做实际工作:

//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()

As you can see the code marked: //Executed after search() has already returned ALWAYS executes after //Still inside search() even if it is before it in the function如您所见,代码标记为: //Executed after search() 已经返回 ALWAYS executes after //Still inside search() 即使它在函数之前

(As of iOS 11.x, the documentation guarantees the completion handler for MKLocalSearch.start will be on the main thread) (从 iOS 11.x 开始,文档保证 MKLocalSearch.start 的完成处理程序将在主线程上)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在completionHandler中使用泛型 - Using Generics in completionHandler 使用Alamofire和completionHandler - Using of Alamofire and completionHandler 是否可以使用MKLocalSearch获取类似“餐馆”的位置类型 - Is that possible to get location type like “restaurant” using MKLocalSearch 在 Swift 中使用 MKLocalSearch 会将一个大头针附加到用户位置而不是蓝点 - Using MKLocalSearch in Swift attaches a pin to the users location instead of the blue dot 我如何从CloudKitcompleteHandler中正确返回值 - How do I return value correctly from CloudKit completionHandler 使用sendAsynchronousRequest:queue:completionHandler:进行URL请求 - Using sendAsynchronousRequest:queue:completionHandler: for URL request 使用时出错 - [MPMediaPlaylist addItemWithProductID:completionHandler:] - Error when using -[MPMediaPlaylist addItemWithProductID:completionHandler:] 使用sendAsynchronousRequest时跟踪重定向:queue:completionHandler:? - Track redirects when using sendAsynchronousRequest:queue:completionHandler:? 带通配符的MKLocalSearch - MKLocalSearch with wildcard 使用sendAsynchronousRequest时显示“数据加载”:queue:completionHandler: - DIsplay 'data loading' while using sendAsynchronousRequest:queue:completionHandler:
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM