I used MapKit's localsearch feature to generate annotation pins of whatever was typed into a search bar. I am now having trouble finding the address of those pinned locations. this is my code for the local search
func performSearch() {
matchingItems.removeAll()
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchText.text;
request.region = attractionsMap.region;
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler({(response:
MKLocalSearchResponse!,
error: NSError!) in
if error != nil {
println("Error occured in search: \(error.localizedDescription)")
} else if response.mapItems.count == 0 {
println("No matches found")
} else {
println("Matches found")
for item in response.mapItems as! [MKMapItem] {
println("Name = \(item.name)")
println("Phone = \(item.phoneNumber)")
matchingItems.append(item as MKMapItem)
println("Matching items = \(matchingItems.count)")
var annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
self.attractionsMap.addAnnotation(annotation)
}
}
})
}
This is my viewForAnnotation method:
extension AttractionsVC: MKMapViewDelegate {
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if(annotation is MKUserLocation) {
return nil;
}
let reuseId = "pin";
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView;
if(pinView == nil) {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId);
pinView!.canShowCallout = true;
pinView!.animatesDrop = true;
}
var moreInfoButton = UIButton.buttonWithType(UIButtonType.DetailDisclosure) as! UIButton;
pinView?.rightCalloutAccessoryView = moreInfoButton;
return pinView;
}
I would now like to be able to display the address on the annotation. Any thoughts would be greatly appreciated.
I still don't understand what it is you want. Sometimes you ask about the address, sometimes you ask about the location.
The location is the latitude and longitude
It is the annotation's coordinate property.
The address is the human postal address
When you called
let search = MKLocalSearch(request: request)
search.startWithCompletionHandler( // ...
...you got back MKMapItems:
for item in response.mapItems as! [MKMapItem] {
Inside each MKMapItem was a placemark (an MKPlacemark, the MKMapItem's placemark property). Inside each placemark was the address information — an MKPlacemark is a CLPlacemark, so consult the CLPlacemark document for how to extract the address info.
Well, you appended each MKMapItem to an array:
matchingItems.append(item as MKMapItem)
That's where the MKMapItems are, unless you've thrown them away; so that's where the address information is.
Related
so i've been learning iOS, and for my practice for MapKit, i have made an app that zooms in on my region and puts a pin at my location (geo coordinates given to the simulator), that all seems to be working and i can even see the pin...
But next i am doing a search for nearby shops with 'Shop' keyword and printing out the names of the search item in the callback handler seem to be showing them. But the pins for that arent appearing on the map.
I googled some, the mapView(mapView:viewFor:)->MKAnnotation doesnt seem to be a lazy function which has to dequeue and make new MKPinAnnotationView objects and return them.... The reason why i checked for lazy is if i print the names of each pin to be returned from inside it doesnt show anything printed, but if i put a breakpoint in there, it will show it then...
Other than that, at first couple of times the pins were showing, so i know the code for adding them is working, but then i added titles to each pin, now i dont know if its taking long to search them and the app finishes its work till then or something or not...
important bits from the code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
locationManager.delegate = self
mapView.delegate = self
if CLLocationManager.authorizationStatus() == .notDetermined {
self.locationManager.requestWhenInUseAuthorization()
}
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
manager.stopUpdatingLocation()
locationManager.delegate = nil
centerMapOnLocation(location: manager.location!)
// Searching for shops
let request = MKLocalSearchRequest()
request.naturalLanguageQuery = "Shop"
request.region = mapView.region
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
let shopPin = MKPointAnnotation()
shopPin.coordinate = item.placemark.coordinate
shopPin.title = item.name
self.mapView.addAnnotation(shopPin)
print("Name = \(item.name!)")
//print(shopPin.title)
}
}
})
// Putting a pin on current location
let homePin = MKPointAnnotation()
homePin.coordinate = (manager.location?.coordinate)!
homePin.title = "BitSol Technologies"
mapView.addAnnotation(homePin)
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let pinIdent = "Pin";
var pinView: MKPinAnnotationView;
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: pinIdent) as? MKPinAnnotationView {
dequeuedView.annotation = annotation;
pinView = dequeuedView;
}else{
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pinIdent);
}
if annotation.coordinate.latitude == locationManager.location!.coordinate.latitude && annotation.coordinate.longitude == locationManager.location!.coordinate.longitude{
pinView.pinTintColor = MKPinAnnotationView.redPinColor();
}else{
pinView.pinTintColor = MKPinAnnotationView.purplePinColor()
}
pinView.canShowCallout = true
print(annotation.title!)
return pinView;
}
Perhaps MKLocalSearch completion handler doesn't run on main thread. Try the following code:
let search = MKLocalSearch(request: request)
search.start(completionHandler: {(response, error) in
// Go back to the main thread to update the UI
DispatchQueue.main.async {
if error != nil {
print("Error occured in search: \(error!.localizedDescription)")
} else if response!.mapItems.count == 0 {
print("No matches found")
} else {
print("Matches found")
for item in response!.mapItems {
let shopPin = MKPointAnnotation()
shopPin.coordinate = item.placemark.coordinate
shopPin.title = item.name
self.mapView.addAnnotation(shopPin)
print("Name = \(item.name!)")
//print(shopPin.title)
}
}
}
})
Call this line of code after adding all annotations on map,
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
I have setup a map in which data is loaded from the parse backend. In map I want to display the images that are already saved in Assets folder. Image should be equal to Player Name . There latitude and longitude is already saved in the Parse.
The position of the player is displayed correctly but the image is not being showed correctly , if the last name in the playerName column is Sachin then the image is same for all the player annotation pin.
I want to apply like if playerName == annotation.title the image of rahul should be displayed .
Console Print :
26.6481591686873 , 77.1777485870544 Sachin
27.655116868732 , 77.17778705437 Rahul
28.6479996556687 , 77.1779252453013 Matt
28.6480628887157 , 77.1779590059193 Virat
28.1578689896 , 76.989079 Sachin
var playerName:String!
override func viewDidLoad() {
super.viewDidLoad()
query.findObjectsInBackgroundWithBlock { (posts, error) in
if error == nil {
let myPosts = posts! as [PFObject]
for posts in myPosts {
let latitude12 = posts["playerLat"] as! Double
let longitude12 = posts["playerLong"] as! Double
let playName = posts["playerName"] as! String
self.playerName = posts["playerName"] as! String
print(latitude12,"," , longitude12, playerName)
let annotation = MKPointAnnotation()
//let customAnno = MKAnnotationView()
let locationCoordinate = CLLocationCoordinate2DMake(latitude12, longitude12)
annotation.coordinate = locationCoordinate
//customAnno.enabled = true
let anno = self.mapView.dequeueReusableAnnotationViewWithIdentifier("places")
if anno == nil {
// if annotation.title == "Sachin" {
//}
annotation.title = playerName
self.mapView.addAnnotations([annotation])
} else {
anno?.annotation = annotation
}
}
}
}
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "customAnnotationView"
let annotation1 = MKPointAnnotation()
// custom image annotation
var annotationView = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier)
if (annotationView == nil) {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: identifier)
}
else {
annotationView!.annotation = annotation
}
if annotation1.title == "Rahul" {
annotationView!.image = UIImage(named: "Rahul")}
else if annotation1.title == "Sachin"{
annotationView!.image = UIImage(named: "Sachin")
}
return annotationView
}
I have tried all codes but the image is same for all the players image.
So I have set up my MKAnnotations in MapView and it's working as I want it to. Now I would like to experiment further and try to change the colors of the pins, what would be the most efficient way to implement this within the following code:
override func viewDidAppear(animated: Bool) {
var annotationQuery = PFQuery(className: "Post")
currentLoc = PFGeoPoint(location: MapViewLocationManager.location)
//annotationQuery.whereKey("Location", nearGeoPoint: currentLoc, withinMiles: 10)
annotationQuery.whereKeyExists("Location")
annotationQuery.findObjectsInBackgroundWithBlock {
(points, error) -> Void in
if error == nil {
// The find succeeded.
println("Successful query for annotations")
// Do something with the found objects
let myPosts = points as! [PFObject]
for post in myPosts {
let point = post["Location"] as! PFGeoPoint
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2DMake(point.latitude, point.longitude)
annotation.title = post["title"] as! String!
annotation.subtitle = post["username"] as! String!
func mapView(aMapView: MKMapView!,
viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let reuseId = "pin"
var pinView = aMapView.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
println("Pinview was nil")
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.pinColor = .Green
return pinView
}
self.mapView.addAnnotation(annotation)
}
} else {
// Log details of the failure
println("Error: \(error)")
}
}
I can't seem to tackle this on my own even though it seem's simple enough. I am also confused about the viewForAnnotation method, from what I could gather it seems like i need to use it, but nothing i'm trying is working.
I know this is a late answer but for anyone else searching, check out the tutorial here which shows exactly how to colour pins
Ray Wenderlich MapView
I wonder how to set different images to annotation pins on mapview. The difference between the following questions
viewForAnnotation confusion and customizing the pinColor iteratively
Swift different images for Annotation
is that my array of images is generated dynamically with regard to server response. There is no fixed size of the array, so switch/case construction is not a good idea. Moreover, I'm not sure how to apply the solution with custom class aforementioned in topic above. I'm aware that it would be better to post a comment to one of the questions asked before, but unfortunately I'm too rookie at the moment to do that(too few points).
This is the for loop performed inside functions that shows map:
for var r=0;r<arrayOfRestaurants.count;r++
{
var summonRestaurant:NSDictionary = arrayOfRestaurants[r] as NSDictionary
var nearbyRestaurant = Restaurant(nearbyRestaurants:summonRestaurant)
var latRestaurant=(nearbyRestaurant.latitude as NSString).doubleValue
var longRestaurant=(nearbyRestaurant.longitude as NSString).doubleValue
var locationOfRestaurant = CLLocationCoordinate2D(
latitude: latRestaurant as CLLocationDegrees, longitude: longRestaurant as CLLocationDegrees)
var lunchArray: NSArray = nearbyRestaurant.lunch as NSArray
var annotation = MKPointAnnotation()
annotation.setCoordinate(locationOfRestaurant)
annotation.title = nearbyRestaurant.name + " " + nearbyRestaurant.distance + " km"
map.addAnnotation(annotation)
}
And here is viewForAnnotation delegate method(quite identical to the method used in aforementioned threads):
func mapView(map: MKMapView!,
viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation is MKUserLocation {
//return nil so map view draws "blue dot" for standard user location
return nil
}
let reuseId = "pin"
var pinView = map.dequeueReusableAnnotationViewWithIdentifier(reuseId) as? MKPinAnnotationView
if pinView == nil {
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView!.canShowCallout = true
pinView!.animatesDrop = true
pinView!.pinColor = .Purple
pinView!.image = globalImageArray[0]
}
else {
pinView!.annotation = annotation
}
return pinView
}
As you can see, I assigned a certain image to pinView which is globalImageArray[0], but I look for a solution that let me iterate over the globalImageArray and assign a certain image to each pin.
I'd be glad to receive any help, thanks in advance!
First, you need to create your own class that adopts the MKAnnotation protocol for your annotations -
class RestaurantAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String
var subtitle: String
var image: UIImage?
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
}
}
Then, use instances of this class when you add the annotation and set the image -
for var r=0;r<arrayOfRestaurants.count;r++
{
var summonRestaurant:NSDictionary = arrayOfRestaurants[r] as NSDictionary
var nearbyRestaurant = Restaurant(nearbyRestaurants:summonRestaurant)
var latRestaurant=(nearbyRestaurant.latitude as NSString).doubleValue
var longRestaurant=(nearbyRestaurant.longitude as NSString).doubleValue
let locationOfRestaurant = CLLocationCoordinate2D(
latitude: latRestaurant as CLLocationDegrees, longitude: longRestaurant as CLLocationDegrees)
var lunchArray: NSArray = nearbyRestaurant.lunch as NSArray
let title = nearbyRestaurant.name + " " + nearbyRestaurant.distance +" km"
var annotation = RestaurantAnnotation(coordinate, title:title, subtitle:"")
annotation.image = globalImageArray[r]
map.addAnnotation(annotation)
}
Now, in your view for annotation you can access the image -
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if !(annotation is RestaurantAnnotation) {
return nil
}
let reuseId = "restaurant"
var anView = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if anView == nil {
anView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
anView.canShowCallout = true
}
else {
anView.annotation = annotation
}
let restaurantAnnotation = annotation as RestaurantAnnotation
if (restaurantAnnotation.image != nil) {
anView.image = restaurantAnnotation.image!
anView.image.layer.setCornerRadius(8.0)
anView.image.layer.clipsToBounds=true
}
else {
// Perhaps set some default image
}
return anView
}
What happens is I type in an address in the search field and click go. The location is found and the map zooms in. I click on the pin image and the title bubble pops up and shows the title.
I created a property "pin" to be a reference to the title property. After dragging to a new location has completed I set the property title label using the reference to the new location. In NSLog the property is showing as changed. But when I tap drag to a new location and dragging has ended the same original value of the title property remains unchanged.
I've looked at tons of questions about similar things and nothing is working.
Here is my GO button method:
#IBAction func didTapGoButton(sender: UIButton) {
self.spinnerContainer.hidden = false
var geocoder = CLGeocoder()
geocoder.geocodeAddressString(searchField.text, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
var region = self.mapView.region as MKCoordinateRegion
region.center = placemark.location.coordinate
region.span.longitudeDelta = 0.0144927536
region.span.latitudeDelta = 0.0144927536
self.mapView.zoomEnabled = true
self.mapView.scrollEnabled = true
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.country)"
self.spinnerContainer.hidden = true
self.mapView.addAnnotation(pa)
self.pin = pa
self.mapView.setRegion(region, animated: true)
self.annotationTitle = pa.title
self.searchField.text = ""
//self.mapView.selectAnnotation(pa, animated: true)
var newLocation = CLLocation(latitude: pa.coordinate.latitude, longitude: pa.coordinate.longitude)
var geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(newLocation, completionHandler: { (placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
// let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
NSLog("\(pa.title)")
self.addressFromCoordinates.text = pa.title
self.noAddressLabel.hidden = true
}
})
}
})
}
My viewForAnimation:
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
if annotation.isKindOfClass(MKUserLocation) {
return nil
}
let reuseId = "pin"
var pin = mapView.dequeueReusableAnnotationViewWithIdentifier(reuseId)
if pin == nil {
NSLog("PIN NIL")
pin = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pin.image = UIImage(named: "pin")
pin.draggable = true
pin.canShowCallout = true
}
else
{
NSLog("PIN NOT NIL")
pin.annotation = annotation
}
return pin;
}
my didChangeDragState method:
func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, didChangeDragState newState: MKAnnotationViewDragState, fromOldState oldState: MKAnnotationViewDragState) {
if newState == MKAnnotationViewDragState.Starting
{
view.dragState = MKAnnotationViewDragState.Dragging
}
else if newState == MKAnnotationViewDragState.Ending || newState == MKAnnotationViewDragState.Canceling
{
view.dragState = MKAnnotationViewDragState.None
var newLocation = CLLocation(latitude: self.pin.coordinate.latitude, longitude: self.pin.coordinate.longitude)
var geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(newLocation, completionHandler: { (placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = pa.title
self.addressFromCoordinates.text = pa.title
self.editAddressButton.hidden = false
self.noAddressLabel.hidden = true
}
})
NSLog("\(self.pin.coordinate.latitude), \(self.pin.coordinate.longitude)")
}
}
Is there some sort of refresh that needs to be done to the map view after dragging?
Thanks for your time
In didChangeDragState, this code:
let pa = MKPointAnnotation()
pa.coordinate = placemark.location.coordinate
pa.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = pa.title
is not updating the title of the annotation just dragged because:
The pa in pa.title = ... is referring to a new instance of MKPointAnnotation which is not connected in any way to the annotation just dragged. The let pa = MKPointAnnotation() line creates a new instance of an MKPointAnnotation. This new instance just exists locally in memory and is not even added to the map.
The update to self.annotationTitle has no effect on the title of the annotation instance that was just dragged because annotationTitle is just some separate string variable you've declared and the original MKPointAnnotation that was added to the map and just dragged has no knowledge or connection whatsoever with annotationTitle (the fact that annotationTitle was initially set equal to the annotation's title in the didTapGoButton does not somehow "link" the two strings together).
In didTapGoButton, you are saving a reference to the annotation object that is actually added (and then dragged) in the pin variable. This pin variable is the reference you can use to update the title of the annotation that was dragged (assuming you will only have one annotation at a time).
So in didChangeDragState, change the code shown above to:
self.pin.coordinate = placemark.location.coordinate
self.pin.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = self.pin.title
However, for your requirements, note that it's not really necessary to keep your own reference to the annotation in the first place...
In didChangeDragState, you can get access to the annotation that was dragged directly from the view argument that is passed into the method. For example:
let ann = view.annotation as MKPointAnnotation
ann.coordinate = placemark.location.coordinate
ann.title = "\(placemark.name), \(placemark.locality), \(placemark.administrativeArea), \(placemark.country), \(placemark.postalCode)"
self.annotationTitle = ann.title
Side Note:
After dragging the annotation and reverse geocoding the new location, there is a good possibility that the geocoded coordinates will be different than where the user dragged the annotation to. This happens because the nearest geocoded address may be slightly further from where the annotation was dragged to. So what will happen is the annotation will move a bit after the user finishes dragging. If you'd rather keep the annotation exactly where the user dragged it, don't update the annotation's coordinate in the geocoder's completion block and only update the title. You may want to set the title to something like "near xyz" if the geocoded address is X meters or more away from the actual coordinates.