Reputation: 3064
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. I have declared myMapItems, which is an array of MKMapItem, and am trying to set it equal to the results of my 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. So:
var myMapItems: [MKMapItem]?
and then later, after defining "region" as an 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.
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]
}
}
Upvotes: 2
Views: 1939
Reputation: 2809
The problem would be that search.start is ASYNCHRONOUS, it will start the request and return before results are done. 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
(As of iOS 11.x, the documentation guarantees the completion handler for MKLocalSearch.start will be on the main thread)
Upvotes: 1