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
Related
When I click Annotation on the map, I want the information about the clicked location to appear in the back view at the bottom.I used Mapkit to create the map
Annotation Array
let annotationLocations = [
["title":"X vet Clinic","latitude":39.895177 , "longitude":32.838194],
["title":"Y Vet Clinic","latitude": 39.894749, "longitude":32.841074],
["title":"Z Vet Clinic","latitude": 39.893615, "longitude":32.841476]
]
With this function, I can show the locations specified in the latitude longitudes above on the map
func createAnnotations(locations: [[String: Any]])
{
for location in locations{
let annotations = MKPointAnnotation()
annotations.title = location["title"] as? String
annotations.coordinate = CLLocationCoordinate2D(latitude: location["latitude"] as! CLLocationDegrees, longitude: location["longitude"] as! CLLocationDegrees)
myMap.addAnnotation(annotations)
}
}
You can subclass your annotation and put the required data in there;
class VetClinicAnnotation: MKPointAnnotation {
let name: String
let address: String
let image: UIImage
init(with name: String, address: String, image: UIImage) {
self.name = name
self.address = address
self.image = image
}
}
Then you can get the annotation info from your map view delegate;
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let annotation = view.annotation as? VetClinicAnnotation {
// Use the annotation data
}
}
First set the delegate
myMap.delegate = self
Then implement
func mapView(_ mapView: MKMapView,didSelect view: MKAnnotationView) {
let ann = view.annotation as! MKPointAnnotation
// use ann
}
I have custom view (a nib with its class) that I show when user taps on map marker. I also have an array of objects from where I'd like to show data of selected object when user taps on one of the markers. I create markers from the same array in a separate method. How to I get my element in array (to show additional data) when user taps the marker ? Or is there another way to get the object from array based on pressed marker. Apart from regexing based on lon and lat etc ?
I solved this by adding my model to
marker.userData
example:
func addMarkers(){
for place in places{
let marker = GMSMarker()
let placeLat = place.latitude
let placeLon = place.longitude
marker.position = CLLocationCoordinate2D(latitude: CLLocationDegrees(placeLat), longitude: CLLocationDegrees(placeLon))
marker.appearAnimation = .pop
marker.map = mpView
marker.userData = place
}
}
Then later I access it in:
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let title = (marker.userData as! placeModel).name
let add = (marker.userData as! placeModel).address
let id = (marker.userData as! placeModel).id
let image = (marker.userData as! placeModel).image
let imageAddressInDocuments = ("\(getDocumentsDirectory())\(image)")
print("image address in documents %#", imageAddressInDocuments )
var infoWindow = Bundle.main.loadNibNamed("custView", owner: self, options: nil)?.first as! custView
infoWindow.titleLabel.text = title
infoWindow.addressLabel.text = add
infoWindow.restaurantImage.image = image != "" ? UIImage(contentsOfFile: imageAddressInDocuments) : UIImage(named: "addImage")
return infoWindow
}
GMSMapViewDelegate has the delegate function which lets you to return
your customView as info window.
optional public func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView?
You have to subclass GSMarker which takes your model in init.
class MapItem:GMSMarker {
var model: Model! //your data model
init(data: Model) {
super.init()
// pass cordinate
self.position = CLLocationCoordinate2D(latitude: data.latitude!, longitude: data.longitude!)
// pass Model
self.model = data
}
}
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
// Wrap GSMarker to your custom marker which is MapItem here
let customMarker = marker as! MapItem
// pass your model to CustomView(marker info window) in init
let customView = CustomView(markerModel: customMarker.model)
return customView
}
I am looking to get data from a Firebase Database into a map. I have a good amount working but am stuck. This is all 100% code, no storyboards.
What I have below works. It will show all pins on a map but I am stuck at that point. I want to be able to tap each pin and get the data for that particular pin. When I do, using the code below I will print out "tap" and the array for MTA.
The data could be shown in a pin annotation/info window or in labels below the map in the View Controller. I am unsure of where to put the code/get it to work. I assume not in the for snap in snapshot but I cannot get the data out for each individual record/pin.
View did load for reference:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.navigationItem.title = stop
mapContainerView.delegate = self
view.addSubview(mapContainerView)
setUpContorller()
fetchTrip()
}
Function to get coordinates for map:
func fetchTrip(){
let ref = FIRDatabase.database().reference()
let tripsRef = ref.child("Trips").child(stop!)
tripsRef.observeSingleEvent(of: .value, with: { (snapshot) in
for snap in snapshot.children {
let tripSnap = snap as! FIRDataSnapshot
if let dict = tripSnap.value as? [String:AnyObject] {
let lat = dict["lat"] as! CLLocationDegrees
let lng = dict["lng"] as! CLLocationDegrees
let MTA = dict["MTAStop"] as! String
let center = CLLocationCoordinate2D(latitude: lat, longitude: lng)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 0.08, longitudeDelta: 0.08))
self.stopMTA.append(MTA)
self.mapContainerView.setRegion(region, animated: true)
let pinCoordinate: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat, lng)
let annotation = MKPointAnnotation()
annotation.coordinate = pinCoordinate
self.mapContainerView.addAnnotation(annotation)
}
}
})
}
Function to tap pin: (I know I need more.. not sure what, possibly a class to hold the data.)
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
// do something
print("tap")
print(stopMTA)
}
Here is an example of the database from simulator testing.
Thanks in advance!
Using MKPointAnnotation() will only place a pin on the map. You need to use a custom class to hold the annotation information and call it with initializers.
Original:
let annotation = MKPointAnnotation()
annotation.coordinate = pinCoordinate
self.mapContainerView.addAnnotation(annotation)
Working version:
let annotation = PinAnnotation(title: MTA, coordinate: pinCoordinate, info: MTA)
annotation.coordinate = pinCoordinate
self.mapContainerView.addAnnotation(annotation)
Also Need:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
}
and
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
}
I download several "events" from CloudKit in my program. Each one contains a picture, title, and location. The second two are used right away and there is no problem. The image is only seen if the user clicks on the info button on the annotation that shows the picture using a separate view controller. I am having trouble getting the images to be passed to the ViewController before it is used while also keeping each event's picture separate.
Here is where each record is queried from CloudKit and put into a for loop that send then to the method below:
func loadEvent(_ completion: #escaping (_ error:NSError?, _ records:[CKRecord]?) -> Void)
{
//...record is downloaded from cloudkit
for record in records
{
if let asset = record["Picture"] as? CKAsset,
let data = NSData(contentsOf: asset.fileURL),
let image1 = UIImage(data: data as Data)
{
self.drawEvents(record["LocationF"] as! CLLocation, title1: record["StringF"] as! String, pic1: image1)
}
}
}
Here is where the variables are assigned and used to create the point annotation (it is a custom one that includes an image):
func drawEvents(_ loc: CLLocation, title1: String, pic1: UIImage)
{
mapView.delegate = self
let center = CLLocationCoordinate2D(latitude: loc.coordinate.latitude, longitude: loc.coordinate.longitude)
let lat: CLLocationDegrees = center.latitude
let long: CLLocationDegrees = center.longitude
self.pointAnnotation1 = CustomPointAnnotation()
self.pointAnnotation1.imageName = pic1
self.pointAnnotation1.title = title1
self.pointAnnotation1.subtitle = "Event"
self.pointAnnotation1.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
self.pinAnnotationView = MKPinAnnotationView(annotation: self.pointAnnotation1, reuseIdentifier: nil)
self.mapView.addAnnotation(self.pinAnnotationView.annotation!)
}
Here is the function that makes EventPage appear when the info button on an MKMapAnnotation is clicked:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let eventPageViewController:EventPageViewController = storyboard?.instantiateViewController(withIdentifier: "EventPage") as! EventPageViewController
self.present(eventPageViewController, animated: true, completion: nil)
}
Here is the ViewController for EventPage:
class EventPageViewController: UIViewController {
#IBOutlet weak var eventPic: UIImageView!
var photo: UIImage!
override func viewDidLoad() {
super.viewDidLoad()
let firstViewController:FirstViewController = storyboard?.instantiateViewController(withIdentifier: "Home") as! FirstViewController
photo = firstViewController.pointAnnotation1.imageName
//photo is nil
eventPic.image = photo
}
}
Intead of storing your annotations in a single instance property (which means that you only have access to the last one), store them in an array, although you don't need to store them at all in order to respond to taps.
var annotations = [CustomPointAnnotation]()
func drawEvents(_ loc: CLLocation, title1: String, pic1: UIImage)
{
mapView.delegate = self
let center = CLLocationCoordinate2D(latitude: loc.coordinate.latitude, longitude: loc.coordinate.longitude)
let lat: CLLocationDegrees = center.latitude
let long: CLLocationDegrees = center.longitude
var newAnnotation = CustomPointAnnotation()
newAnnotation = CustomPointAnnotation()
newAnnotation.imageName = pic1
newAnnotation.title = title1
newAnnotation.subtitle = "Event"
newAnnotation.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: long)
self.mapView.addAnnotation(newAnnotation)
self.annotations.append(newAnnotation)
}
The annotation view that is tapped is passed to your delegate method, so you know which item it is. The annotation view's annotation property is your CustomPointAnnotation instance.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let eventPageViewController:EventPageViewController = storyboard?.instantiateViewController(withIdentifier: "EventPage") as! EventPageViewController
if let annotation = view.annotation as CustomPointAnnotation {
eventPageViewController.photo = annotation.imageName
}
self.present(eventPageViewController, animated: true, completion: nil)
}
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
}