Swift - setting different images from array to annotation pins - ios

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
}

Related

Coordinates are nil when selecting annotation on map Swift

I'm changing one part of my app from displaying some shops in a UITableview to display them on a MKMapView and they display fine but I'm getting nil coordinate values when I select the annotations on the map. In didAdd I print them and they are fine, and in fact the annotation is displayed on map.
It has been a while since I last used MapKit and I can't spot if the problem is in the custom annotation class or somewhere else. Can you spot where I'm misusing it?
As always many thanks for you time and help.
This is the annotation class:
class ShopAnnotation: NSObject , MKAnnotation {
var title: String?
var coordinate:CLLocationCoordinate2D
var image: UIImage?
var shopName: String?
var shopLogoUrl: String?
init(title: String, iconImage: UIImage, coordinate:CLLocationCoordinate2D, shopName: String, shopLogoUrl: String) {
self.coordinate = coordinate
self.title = title
self.shopName = shopName
self.shopLogoUrl = shopLogoUrl
self.image = iconImage
}
}
than I have a an array that I take values from :
var availableShopsArray:[(name:String, logoUrl:String, homeLat: String, homeLong: String)] = []
I than loop through it and place an annotation on the map:
func displayShops() {
mapView.removeAnnotations(mapView.annotations)
for shop in availableShopsArray {
let coordinates: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: Double(shop.homeLat)!, longitude: Double(shop.homeLong)!)
let title = "Route Mid"
let image = UIImage(named: title)!
let shopAnn: ShopAnnotation = ShopAnnotation(title: title, iconImage: image, coordinate: coordinates, shopName: shop.name, shopLogoUrl: shop.logoUrl)
mapView.addAnnotation(shopAnn)
}
// self.availableShopsTableview.reloadData()
}
when I select one I should take the values from it, but coordinate is nil :
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let selectedShop = view.annotation as? ShopAnnotation else {return}
self.shopLogoUrl = selectedShop.shopLogoUrl!
self.selectedShopCoordinates.latitude = selectedShop.coordinate.latitude
self.selectedShopCoordinates.longitude = selectedShop.coordinate.longitude
self.selectedShopName = selectedShop.shopName
// calculateBookingType()
self.performSegue(withIdentifier: "bookingToCartSegue", sender: self)
}
and this is viewForAnnotation :
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { // rajish version
let annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: "")
if annotation is MKUserLocation{
return nil
} else {
print("Annotation.coordinates are: \(String(describing: annotation.coordinate))")
// print("annotation title is: \(String(describing: annotation.title!))")
annotationView.image = UIImage(named:(annotationView.annotation?.title)! ?? "")
annotationView.canShowCallout = true
let transform = CGAffineTransform(scaleX: 0.27, y: 0.27) // alert annotation's icons size
annotationView.transform = transform
// makin allerts draggable
annotationView.isDraggable = false
return annotationView
}
}
Weirdly enough I was assigning latitude and longitude separately to selectedShopCoordinates from selectedShop.coordinateand on that it would find nil.
I now assign them as coordinates as selectedShopCoordinates = selectedShop.coordinate and is not crashing anymore.
Why would it find nil on single coordinates degrees is still a mystery.
Any idea on why would be very much appreciated.
Hope this will help others struggling with it.
Cheers

Swift adding annotations on map

Trying to make some annotations on the map. But can't get around it. the code I'm using is
// Names
names = ["Ben", "Big", "Hawk", "Enot", "Wiltons", "Scott's", "The Laughing"]
// Latitudes, Longitudes
coordinates = [
[51.519066, -0.135200],
[51.513446, -0.125787],
[51.465314, -0.214795],
[51.507747, -0.139134],
[51.509878, -0.150952],
[51.501041, -0.104098],
[51.485411, -0.162042],
[51.513117, -0.142319]
]
The function addAnnotation takes an array of type CLLocation, which is what you should use. So make the array look like this to initialize CLLocation objects. I assume you already have a working rendered map, mapView object, etc.
let coords = [ CLLocation(latitude: xxxx, longitude: xxxx),
CLLocation(latitude: xxx, longitude: xxx),
CLLocation(latitude: xxx, longitude:xxx)
];
here is a function that can take that array, and loops through each element and adds it as an annotation to the mapView (not yet rendered)
func addAnnotations(coords: [CLLocation]){
for coord in coords{
let CLLCoordType = CLLocationCoordinate2D(latitude: coord.coordinate.latitude,
longitude: coord.coordinate.longitude);
let anno = MKPointAnnotation();
anno.coordinate = CLLCoordType;
mapView.addAnnotation(anno);
}
}
Finally, you need to use the MKMapViewDelegate delegate to use one of its automatically called methods, when a new annotation is added, so we can queue and render it. This would be unnecessary if you were adding annotations once, but it is good to have them added like this so you can manipulate them later.
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation{
return nil;
}else{
let pinIdent = "Pin";
var pinView: MKPinAnnotationView;
if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(pinIdent) as? MKPinAnnotationView {
dequeuedView.annotation = annotation;
pinView = dequeuedView;
}else{
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: pinIdent);
}
return pinView;
}
}
Do not just add this code and expect it to work, make sure you incorporate it correctly, in the right areas of your project. Do comment if you have further questions.
UPDATE:
remember to inherit the MKMapViewDelegate protocol into the controller your using.
Make sure you actually call addAnnotations, in ViewDidLoad, and pass through coords array. Which can be defined in ViewDidLoad.
Make sure the mapView method is not in ViewDidLoad but a member of the controller.
Add Simple annotation with title Swift 5.0
let addAnotation = MKPointAnnotation()
addAnotation.title = "YOUR TITLE"
addAnotation.coordinate = CLLocationCoordinate2D(latitude: [YOUR LATITIUDE], longitude: [YOUR LONGITUDE])
self.mapView.addAnnotation(addAnotation)
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard annotation is MKPointAnnotation else { return nil }
let identifier = "Annotation"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView!.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
return annotationView
}

Associating different pin images with different locations iOS

I have an array of 4 locations, and an array of 4 pin names (ie "bluePin.png | redPin.png | etc...")
Currently I'm using a bit of code I found elsewhere to add custom pin graphics to all 4 locations, but all of my pins are red.
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
let annotationReuseId = "Truck"
var trkPin = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationReuseId)
if trkPin == nil {
trkPin = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationReuseId)
} else {
trkPin!.annotation = annotation
}
trkPin!.image = UIImage(named: "redPin.png")
trkPin!.backgroundColor = UIColor.clearColor()
trkPin!.canShowCallout = false
return trkPin
}
How could I associate the pin colors with the locations in the array, so that each location is differentiated by color?
Thanks!
I have done this in the simple way by declaring array of location and array images. And I have viewDidLoad method as follows.
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
let location = [
CLLocationCoordinate2D(latitude: 12, longitude: 77),
CLLocationCoordinate2D(latitude: 12.5, longitude: 77.5),
CLLocationCoordinate2D(latitude: 13, longitude: 78),
CLLocationCoordinate2D(latitude: 13.5, longitude: 78)
]
let pins = [
"red.png",
"blue.png",
"green.png",
"yellow.png"
]
var annotations = [MKPointAnnotation]()
for (index, eachLocation) in location.enumerate() {
let pinImageName = pins[index]
let annotation = MKPointAnnotation()
annotation.coordinate = eachLocation
annotation.title = "\(pinImageName)"
annotations.append(annotation)
}
mapView.addAnnotations(annotations)
}
And in the viewForAnnotation delegate method just getting that image name from the title property and explicitly disabling the callout
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let annotationReuseId = "Truck"
var trkPin = mapView.dequeueReusableAnnotationViewWithIdentifier(annotationReuseId)
if trkPin == nil {
trkPin = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationReuseId)
trkPin!.canShowCallout = false
} else {
trkPin!.annotation = annotation
if let image = annotation.title {
trkPin!.image = UIImage(named: image ?? "default.png")
} else {
trkPin!.image = UIImage(named: "default.png")
}
}
return trkPin
}

Attempting to change MKAnnotation color within my code?

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

Parse objects as AnnonationPoints

I'm trying to show a set of parse objects in a MKMapView as Annonations. This seem to work, the problem is that the objects images are swapped, which is kind of random? i've started creating a subclass of the MKPointAnnotation in order to save the image. How come the images are swapped around?
Custom MKPointAnnonation class
class AnnonationClass: MKPointAnnotation {
var itemId: NSString?
var itemImage: PFFile?
var itemdescription: NSString?
}
getting the parse objects and adding them using the setGeoPoint method
override func objectsDidLoad(error: NSError!) {
super.objectsDidLoad(error)
if error == nil {
self.pointMapView?.removeAnnotations(pointMapView?.annotations)
for object in objects {
var theObject = object as PFObject
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd" // superset of OP's format
let str = dateFormatter.stringFromDate(object.createdAt)
self.setGeoPoint(theObject.objectForKey("location") as PFGeoPoint, titleString: theObject.objectForKey("title") as NSString, imageFile: theObject.objectForKey("image") as PFFile, theId: theObject.objectId as NSString, descString: theObject.objectForKey("description") as NSString, dateString: str)
}
}
}
Setting the coordinates, title and saving the imageFile in the itemImage
func setGeoPoint(geoPoint:PFGeoPoint, titleString:NSString, imageFile:PFFile, theId:NSString, descString:NSString, dateString:NSString) {
var coordinate = CLLocationCoordinate2DMake(geoPoint.latitude, geoPoint.longitude) as CLLocationCoordinate2D
var pinView:AnnonationClass = AnnonationClass()
pinView.itemImage = imageFile
pinView.setCoordinate(coordinate)
pinView.title = titleString
pinView.subtitle = dateString
self.pointMapView!.addAnnotation(pinView)
}
creating the image and setting it
func mapView(mapView: MKMapView!, viewForAnnotation annotation: AnnonationClass!) -> MKAnnotationView! {
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
pinView!.pinColor = .Purple
var leftImage: PFImageView = PFImageView(frame: CGRectMake(0, 0, 44, 44))
leftImage.file = annotation.itemImage
leftImage.loadInBackground()
pinView!.leftCalloutAccessoryView = leftImage
}
else {
pinView!.annotation = annotation
}
return pinView
}
In your viewForAnnotation, when pinView != nil, i.e. when reusing a view, it could well be that the image is set from the previous time that view was used. I think it is analogous to setting check marks on table view cells instead of using the data source to properly set properties for the cell contents.
Try setting the image view whether or not the pinView is reused.
func mapView(mapView: MKMapView!, viewForAnnotation annotation: AnnonationClass!) -> MKAnnotationView! {
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
pinView!.pinColor = .Purple
}
else {
pinView!.annotation = annotation
}
var leftImage: PFImageView = PFImageView(frame: CGRectMake(0, 0, 44, 44))
leftImage.file = annotation.itemImage
leftImage.loadInBackground()
pinView!.leftCalloutAccessoryView = leftImage
return pinView
}

Resources