How to turn a MKPointAnnotation to a button? - ios

Whenever a user taps on an MKPointAnnotation, I want him to be redirected to a specific view. My problem is how do I get the annotation to perform an action when tapped?
Here is my code:
for i in closestpharmacyname {
var docref2 = db.collection("pharmacies").document(i)
print("Pharmacy: ", i)
docref2.getDocument(source: .cache) { (document, error) in
if var document = document {
var pharmacylatitude = document.get("latitude") as! Double
var pharmacylongitude = document.get("longitude") as! Double
print(pharmacylatitude, pharmacylongitude)
var pharmacyannotation = MKPointAnnotation()
pharmacyannotation.coordinate = CLLocationCoordinate2D(latitude: pharmacylatitude, longitude: pharmacylongitude)
pharmacyannotation.title = i
self.MapView.addAnnotation(pharmacyannotation)
} else{
print("Document Does not exist")
}
}
}

To do this, you need to add:
yourAnnotationView?.rightCalloutView = UIButton(type: UIButtonType.detailDisclosure)
into your mapView(_:viewFor:) function. This will change the looks of your annotation, but there will be a button on the side that you can press to do different tasks.The annotation will look something like this:
In order to handle these tasks, you also need to add a new function:
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
//perform tasks here
}

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

My button I created progmatically to push from my app the Apple Maps app stopped working.. why?

I have a navigation app I am working on which works like a normal maps app, allowing you to search for locations through a search table etc. So in my app I have a button which allows for the user to add multiple annotations (be default every time the user clicks a new result in the searchtable I have it set to remove the most recent annotation) and find the centerpoint between all these areas. The code then shifts that centerpoint to the closest address so there is a place to get directions to, not just lat and long. Anyway, I have it set so that when the user taps the annotation, a button appears right above it, and the button is supposed to send you to apple maps with the preset route already loaded in. This worked until recently. Here is the code that carries this button's actions out:
extension ViewController : MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor 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 = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
pinView?.pinTintColor = UIColor.red
pinView?.canShowCallout = true
let smallSquare = CGSize(width: 30, height: 30)
let button = UIButton(frame: CGRect(origin: (CGPoint()), size: smallSquare))
button.setBackgroundImage(UIImage(named: "car"), for: [])
button.addTarget(self, action: #selector(getDirections), for: UIControlEvents.touchUpInside)
pinView?.leftCalloutAccessoryView = button
return pinView
}
}
and in the class
#objc func getDirections(){
if let selectedPin = selectedPin {
let mapItem = MKMapItem(placemark: selectedPin)
let launchOptions = [MKLaunchOptionsDirectionsModeKey : MKLaunchOptionsDirectionsModeDriving]
mapItem.openInMaps(launchOptions: launchOptions)
}
}
Could it be something weird with the objective C function? This is the code I recently added that potentially could have caused the issue, though I really don't know:
func resolveAddress(for averageCoordinate: CLLocationCoordinate2D, completion: #escaping (MKPlacemark?) -> () ) {
let geocoder = CLGeocoder()
let averageLocation = CLLocation(latitude: averageCoordinate.latitude, longitude: averageCoordinate.longitude)
geocoder.reverseGeocodeLocation(averageLocation) { (placemarks, error) in
guard error == nil,
let placemark = placemarks?.first
else {
completion(nil)
return
}
completion(MKPlacemark(placemark: placemark ))
}
}
#IBAction func middleFinderButton(_ sender: Any) {
let totalLatitude = mapView.annotations.reduce(0) { $0 + $1.coordinate.latitude }
let totalLongitude = mapView.annotations.reduce(0) { $0 + $1.coordinate.longitude }
let averageLatitude = totalLatitude/Double(mapView.annotations.count)
let averageLongitude = totalLongitude/Double(mapView.annotations.count)
let centerPoint = MKPointAnnotation()
centerPoint.coordinate.latitude = averageLatitude
centerPoint.coordinate.longitude = averageLongitude
mapView.addAnnotation(centerPoint)
resolveAddress(for: centerPoint.coordinate) { placemark in
if let placemark = placemark {
self.mapView.addAnnotation(placemark)
} else {
self.mapView.addAnnotation(centerPoint)
}
}
print(totalLatitude)
print(totalLongitude)
print(averageLatitude)
print(averageLongitude)
print(centerPoint.coordinate)
}
}
Anyone have an idea why this button isn't working and pushing the app to maps with a preset route? could it be a glitch with the simulator(it used to work so I doubt it)? Thanks.

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

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
}

Resources