Assigning an accessibility identifier to an MKMapView object - swift

I'm trying to configure snapshot for an app I'm working in, it already works in the english version but in the localized versions I don't know how to assign an accessibility identifier to a pin in the Map in MKMapView, somebody knows hows to do this?
Thanks.

Accessibility identifiers are a great way to separate your application's language from Xcode UI Testing. The identifier comes from UIAccessibilityIdentification which UIView already conforms to. However, neither NSObject nor MKAnnotation conform to the protocol. So you have to set that conformance yourself.
class Annotation: NSObject, MKAnnotation, UIAccessibilityIdentification {
let coordinate: CLLocationCoordinate2D
let title: String?
var accessibilityIdentifier: String?
init(title: String?, coordinate: CLLocationCoordinate2D) {
self.title = title
self.coordinate = coordinate
}
}
let coordinate = CLLocationCoordinate2DMake(40.703490, -73.987770)
let annotation = Annotation(title: "BeerMenus HQ", coordinate: coordinate)
annotation.accessibilityIdentifier = "Custom Identifier"
let mapView = MKMapView()
mapView.addAnnotation(annotation)
Then under test you can reference the annotation via otherElements.
let app = XCUIApplication()
let annotation = app.maps.element.otherElements["Custom Identifier"]
annotation.tap()

Related

Problem with callout button and firestore in swift

I have 5 collections on Firestore. With the data of the documents of those collections, I create annotations of different types and display them on a map view.
The problem is that I want to pass all the information that is stored in each annotation and display it on another view controller, that appears when you press the callout button.
I can't figure out a way of referencing the annotation I'm pressing and then pass the data to the other screen.
This is my first time using databases and I don't have many experience, so I would appreciate some help.
Thanks!
It's difficult to give a specific answer since I don't know the full scope of your features. But this is how you generally do it.
First when you create the MKAnnotation subclass, you define a property to hold an object that you can reference later. For example, say I'm showing restaurants and supermarkets in a map.
class RestaurantAnnotation: NSObject, MKAnnotation {
let restaurant: Restaurant
var title: String? {
return restaurant.name
}
var coordinate: CLLocationCoordinate2D {
return restaurant.coordinate
}
init(restaurant: Restaurant) {
self.restaurant = restaurant
super.init()
}
}
struct Restaurant {
let name: String
let coordinate: CLLocationCoordinate2D
}
Same goes for the supermarkets.
Then when you create the annotation, you pass the Restaurant object to it.
let restaurant = Restaurant(name: "McDonald's", coordinate: CLLocationCoordinate2D(latitude: 27.2831, longitude: -127.831))
let restaurantAnnotation = RestaurantAnnotation(restaurant: restaurant)
mapView.addAnnotation(restaurantAnnotation)
You implement the mapView(_:annotationView:calloutAccessoryControlTapped:) delegate method to be notified when the user taps on the callout button in annotations. In it, you can easily reference the object you passed to it earlier.
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
if let annotation = view.annotation as? RestaurantAnnotation {
print(annotation.restaurant)
} else if let annotation = view.annotation as? SupermarketAnnotation {
print(annotation.supermarket)
}
}
And you can use that data to do whatever you want after that. In your case, pass it to a new screen.

Swift handle did select on multiple annotations on same location

I'm trying to call a function when annotations of the same location are selected. All I'm aiming for is to call a function and show a view controller with the two restaurants but for some reason, the function below isn't being called when I selected the marker in the image. (It works fine for single annotations). Thanks for any help.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
guard let anno = view.annotation as? MyAnno else { return }
print(anno.post)
}
The problem lies in your guard statement. As I could find out in a short research, iOS uses a MKClusterAnnotation type for annotations that are equally located. Thus, the view variable does not have a .annotation set that confirms to your custom subclass.
In this case you need to try to cast it to a MKClusterAnnotation object, which in return has a property .memberAnnotations of type [MKAnnotation], which is what you want.
I think your code should look something like this (haven't tested it though):
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let anno = view.annotation as? MyAnno {
print("Single Annotation selected:")
print(anno.post)
// do anything
return
}
if let anno = view.annotation as? MKClusterAnnotations {
let selection: [MKAnnotation] = anno.memberAnnotations
print("\(selection.count) Annotations selected:")
// do something with your annotation group
// you should now also be able to cast to your MyAnno class again
for item in selection {
if let myAnno = item as? MyAnno {
print(myAnno.post)
}
}
}
}
For further information also take a look at Decluttering a Map with MapKit Annotation Clustering.

Swift - GoogleMaps SDK get coordinates on touch

I am new to swift and the Google Maps SDK, and was wondering how to get the coordinates of where the user has tapped using the Google Maps SDK. For example if a user holds their finger down on a certain place on a map, a annotation is created there. I would really appreciate your help, thanks.
In the GMSMapViewDelegate there is a method named: mapView:didLongPressAtCoordinate: which is called after a long-press gesture at a particular coordinate. See the reference here.
By implementing this method you could then add a marker to the map view:
func mapView(mapView: GMSMapView!, didLongPressAtCoordinate coordinate: CLLocationCoordinate2D) {
let marker = GMSMarker(position: coordinate)
marker.title = "Hello World"
marker.map = mapView
}
For a tap gesture a similar delegate method can be implemented called mapView:didTapAtCoordinate: which can be used in a similar way:
func mapView(mapView: GMSMapView!, didTapAtCoordinate coordinate: CLLocationCoordinate2D) {
print("Tapped at coordinate: " + String(coordinate.latitude) + " "
+ String(coordinate.longitude))
}
Try this
extension ViewController: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D)
{
print("Tapped at coordinate: " + String(coordinate.latitude) + " "
+ String(coordinate.longitude))
}
}
For Swift 5.0+
First, make sure you have added GMSMapViewDelegate delegate to your ViewController Class
Add this default function in your class
func mapView(_ mapView: GMSMapView, didLongPressAt coordinate: CLLocationCoordinate2D) {
debugPrint("Coordinates: ", coordinate)
}
If you just want coordinates, then above function is perfect for you But if you want to create Marker or get local address from touch then see below function
func mapView(_ mapView: GMSMapView, didLongPressAt coordinate: CLLocationCoordinate2D) {
let marker = GMSMarker(position: coordinate) //Add this line if you want to add marker
let decoder = CLGeocoder()
decoder.reverseGeocodeLocation(CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)) { placemarks, err in
if let placeMark = placemarks?.first {
let plName = placeMark.name ?? placeMark.subThoroughfare ?? placeMark.thoroughfare! //Place Name
var address : String! = "" //This will be the local address
if let subLocality = placeMark.subLocality ?? placeMark.name {
address.append(subLocality)
address.append(", ")
}
if let city = placeMark.locality ?? placeMark.subAdministrativeArea {
address.append(city)
address.append(", ")
}
if let state = placeMark.administrativeArea, let country = placeMark.country {
address.append(state)
address.append(", ")
address.append(country)
}
// Add Marker:
marker.title = plName
marker.snippet = address
marker.appearAnimation = .pop
marker.map = mapView
}
}
}
This Function does not only get coordinates but creates a Marker with all the details fetched from the coordinates(Like PlaceName, city, state,country etc.) too
If you just want local address, then remove all the code lines related to marker
The reason why i have used CLGeocoder and not GMSGeocoder from
GoogleMapDelegate is that Apple's CLGeocoder is much more precise
in getting the place-name while GMSGeocoder does not fetch
place-name accurately.
Also Note that : Apple's Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may
cause some of the requests to fail.

Use detailCalloutAccessoryView as third Annotation property

I am a newbie at Swift as well as Stackoverflow, and I was wondering if there is a way to import data from my plist to detailCalloutAccesoryView in the same manner as I import to "title" and "subtitle" in my map annotation callouts? This way, I could avoid having to make my own custom map annotation, and rather use the built-in features of MapKit.
I am calling my variables to title and subtitle like this:
var myData: NSArray?
if let path = NSBundle.mainBundle().pathForResource("myData", ofType: "plist") {
myData = NSArray(contentsOfFile: path)
}
if let items = myData {
for item in items {
let lat = item.valueForKey("Latitude") as! Double
let long = item.valueForKey("Longitude") as! Double
let myAnnotation = Mydata(value1: "Value1", value2: "Value2", value3: "Value3", latitude: lat, longitude: long)
// Define "Value1 to be shown as title in Callout
myAnnotation = item.valueForKey("Value1") as? String
// Define "Value2 to be shown as subtitle in Callout
myAnnotation.subtitle = item.valueForKey("Value2") as? String
annotations.append(myAnnotation)
}
}
return annotations
}
By now, I am just showing the same value in all annotations for detailCalloutAccessoryView in the place of subtitle using the following piece code:
let detailAccessory = UILabel(frame: CGRectMake(0,0,50,30))
detailAccessory.text = "Value3" // Obviously, shows constant value for all annotations
detailAccessory.font = UIFont(name: "Verdana", size: 10)
pinView?.detailCalloutAccessoryView = detailAccessory
Please, do not let my ignorance annoy you...
You will need to implement func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? delegate method. In there you can customize detailCalloutAccessoryView.
For detailed explanation refer to https://developer.apple.com/videos/play/wwdc2015/206/

mapView calloutAccessoryControlTapped, how to know who launched it?

I'm working on a project with a map, there are some annotations whose views have a detail button.
Those annotations are created in a for loop where I get data in JSON format, and for every element an annotation is created.
However there's no direct correlation between Annotations (or AnnotationViews) and those elements.
...
anView?.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
...
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
self.performSegueWithIdentifier("openInfo", sender: control)
}
I understand that this button calls the mapView function with calloutAccessoryControlTapped, and passes the AnnotationView.
The problem is that, even if I know the AnnotationView that called the segue, I don't know whose this responds to.
Is there any way to save some informations in the AnnotationView? I mean, so that every instance of AnnotationView has an ID that can relate to the data i received?
Any other idea?
Thanks
You can access the selected annotation from:
let selectedAnnotation = view.annotation as! CustomAnnotation // See structure below.
And if you have a data source which you want to access, it's recommended to create a custom object for your MKAnnotations that contain some reference to your data source, like an identifier. And then set:
selectedId = selectedAnnotation.id
performSegueWithIdentifier("openInfo", sender: control)
And in your prepareForSegue pass the selectedId to the receiving view controller.
EDIT: Your annotation class could look something like this:
class CustomAnnotation: NSObject, MKAnnotation {
var title: String?
var coordinate: CLLocationCoordinate2D
var subtitle: String?
var id: Int
init(title: String, coordinate: CLLocationCoordinate2D, subtitle: String, id: Int) {
self.title = title
self.coordinate = coordinate
self.subtitle = subtitle
self.id = id
}
}