Passing an object to custom view in Google maps SDK - ios

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
}

Related

How can I show the annotation point information on the map?

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
}

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 3 - LongPress on GMSMarker and print its coordinates (Google Maps iOS SDK)

Using Google Maps iOS SDK, I want to be able to print the coordinates of a GMSMarker if long pressed on that particular marker.
I first get all of my coordinates from a dictionary and drop markers for all coordinates on the map:
func placeMapMarkers() {
for item in self.finalDictionary as [Dictionary<String, String>] {
let lat = item["lat"] as! String
let lon = item["lon"] as! String
let SpotLat = Double(lat)
let SpotLon = Double(lon)
let SpotLocation = CLLocationCoordinate2DMake(SpotLat!, SpotLon!)
DispatchQueue.main.async(execute: {
self.SpotMarker = GMSMarker(position: SpotLocation)
self.SpotMarker?.icon = self.imageWithImage(image: UIImage(named: "SpotIcon")!, scaledToSize: CGSize(width: 35.0, height: 35.0))
self.SpotMarker?.title = "Long press to navigate here"
self.SpotMarker?.map = self.mapView
})
longPress(mapView: self.mapView, didLongPressAtCoordinate: SpotLocation)
}
}
My longPress function call is in the above placeMapMarkers function itself, since I want to identify while placing markers itself if a particular marker has been long pressed (I could be wrong with my thinking here).
My longPress function is below.
//This is long Press function:-
func longPressView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D) {
//Here handle your long press on map marker like:-
print("long pressed at \(coordinate)")
}
The problem is that I am getting "long pressed at" all coordinates from the dictionary of coordinates.
I want to
place markers for all coordinates in a dictionary
long press on a particular marker
and print coordinates for only that particular marker which was long pressed.
How do I go about this? Had a look at the other solutions, wasn't able to work out much.
I looked through the GMSMapView API. There is a method called "didLongPressAtCoordinate" that passes in a CLLocationCoordinate2D, so I think you can use that to create a GMSMarker. See here
You have to implement the GMSMapViewDelegate, and you can call
func mapView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D) {
let marker = GMSMarker(position: coordinate)
marker.title = "Found You!"
marker.map = mapView
}
Hope this one helps :)
I have done this before. You basically have to convert the touch point on the map to a coordinate.
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(userPerformedLongPress(gesture:)))
uilpgr.minimumPressDuration = 2.0
}
func userPerformedLongPress(gesture: UIGestureRecognizer) {
let touchPoint = gesture.location(in: mapView)
let newCoordinate: CLLocationCoordinate2D = mapView.convert(touchPoint, toCoordinateFrom: mapView)
let annotation = MKPointAnnotation()
annotation.coordinate = newCoordinate
annotation.title = "Location Selected"
annotation.subtitle = "Coordinate: \(round(1000*newCoordinate.longitude)/1000), \(round(1000*newCoordinate.latitude)/1000)"
mapView.addAnnotation(annotation)
print("Gesture recognized")
}

Swift 3 Firebase to MapKit

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

Sending Images to Several Instances of a ViewController in Swift

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

Resources