How to identify if MKPointAnnotation has been pressed in Swift? - ios

Using a for loop I have stored a unique URL in each annotation using a tag create in a class named CustomPointAnnotation. I am trying to print out the URL of the annotation that has been pressed. The problem is my output console in Xcode prints nothing when I click on an annotation.
I tried to follow this guide: How to identify when an annotation is pressed which one it is
I replicated all the code but it is not detecting if the annotation was clicked.
How do I know if the annotation is clicked?
Here is the CustomPointAnnotation.
class CustomPointAnnotation: MKPointAnnotation {
var tag: String!
}
I declared the variable tag so I can store a unique variable for each annoation.
My ViewController class:
In the ViewController class there is a loop which iterates through my Firebase Database JSON files:
func displayCordinates() {
ref = Database.database().reference()
let storageRef = ref.child("waterfountains")
storageRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children.allObjects as! [DataSnapshot] {
let annotation = CustomPointAnnotation()
let dict = child.value as? [String : AnyObject] ?? [:]
annotation.title = "Water Fountain"
annotation.tag = dict["url"] as? String
annotation.coordinate = CLLocationCoordinate2D(latitude: dict["lat"] as! Double, longitude: dict["long"] as! Double)
self.mapView.addAnnotation(annotation)
}
})
}
The annotations are displayed by calling the functions in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
displayCordinates()
}
A function which should detect if an annotation has been clicked:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let annotation = view.annotation as? CustomPointAnnotation {
print(annotation.tag!)
}
}
Thank you for helping.

mapView:didSelect: is a MKMapViewDelegate method. If you do not set mapView.delegate = self on your ViewController this function will never get triggered.
Typically it would get set in ViewDidLoad. before performing any other operations with the mapView. Changing your ViewDidLoad to look like
override func viewDidLoad() {
super.viewDidLoad()
self.mapView.delegate = self
displayCordinates()
}
should fix your issue. For more information on the protocol/delegate design pattern all over the apple frameworks, I suggest this swift article from the Swift Programming Guide.
More specifically to your case, check out all the other functionality/control you can bring with your implementation of MKMapView on your ViewController by checkout out the apple docs on MKMapViewDelegate. This will cover things like monitoring when the map finishes loading, when it fails, when the user's location is updated, and more things you may want to increase the functionality of your app and provide a great user experience.

Related

Plotting a Specific Location on map view with latitude and longitude

I want to plot a specific point on the map having the lat and lon from an api.
Program flow:
Get LAT & LON from api (done)
Ping api again via timer after every 5 seconds to get the latest location (done)
Plot location with retrieved LAT & LON on map
The issue is every code on the net has to do with 2 points, so user loc and destination loc. I cant seem to get it to work without user loc. I have however coded this to plot the location. However, with this when I touch the map, the map zooms out. Another issue is when I get another point the previous one also remains on the screen. (for testing purpose I hard coded the lat and lon but when I connect the api code that refreshes, prior points remain and the map code is the same as this. The lat and lon are passed via func parameters in createAnnotation().)
My code:
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self // or connect in storyboard
createAnnotation()
}
func createAnnotation(){
let annotations = MKPointAnnotation()
annotations.coordinate = CLLocationCoordinate2D(latitude: 41.87369, longitude: -87.813293)
mapView.addAnnotation(annotations)
}}
How Do I plot the coordinates properly? and then delete the prior and show the new one?.
For the "previous one also remains on the screen" problem: don't keep making a new annotation and calling addAnnotation if you don't want to keep adding new annotations. Instead, keep hold of the annotation that you add, and move it later using its coordinate property. Something like this maybe:
class ViewController: UIViewController, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
var annotationForThing: MKPointAnnotation?
var coordinateOfThing: CLLocationCoordinate2D? {
didSet {
guard let newCoord = coordinateOfThing else {
if let existing = annotationForThing {
mapView.removeAnnotation(existing)
}
return
}
if let existing = annotationForThing {
existing.coordinate = coordinateOfThing
}
else {
let newAnnotation = MKPointAnnotation()
newAnnotation = coordinateOfThing
mapView.addAnnotation(newAnnotation)
annotationForThing = newAnnotation
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
mapView.delegate = self // or connect in storyboard
}

Pass data to from one viewcontroller to another viewcontroller on the same view

I'm building my first app and I have two view controllers being displayed at the same time. The first is my google maps and top on of that view is a panel(pod) I found. The Googlemaps viewcontroller calls up the panel which is in a separate story board.
I'd like to be able to pass data to the panel view without having to use a segue. I have tried the segue approach but it totally replaces the whole view. Is there a way for me to pass Data to the panel without having to switch to that view alone?
extension MapViewController: GMSMapViewDelegate {
func mapView(_ mapView: GMSMapView, didTap marker: GMSMarker) -> Bool {
guard let placeMarker = marker as? PlaceMarker else {return false}
guard let panview = self.storyboard?.instantiateViewController(withIdentifier: "atminfo") as? PanelMaterial
else {
return false
}
panview.Titlee = placeMarker.place.name
atmaddress = panview.Titlee ?? "Me"
print(atmaddress)
return false
}
}
Above is my code, this is one the GoogleMaps ViewController, this is where I receive the info from the pin and try pass it to the Panel that is active in the same view. I'm able to receive the data when I put breakpoints, but passing the data whilst the panel is still active in my GoogleMaps View doesn't display.
Any help is appreciated.
you can also use singleton classes to passing the data to different controller or you can create object in appdelegate it can also used from any class.
like this:
Declare variable in appdelegate
var channelID = ""
you can use like
let kAppDelegate = UIApplication.shared.delegate as! AppDelegate
print(kAppDelegate.channelID)

how to match (or compare) taps to annotations?

EnvironmentXcode 8Swift 3
Problem Statement
I want to be able to determine if a user taps on a MKPointAnnotation and then extract information (like title and subtitle) from that annotation for use within my app.
I imagine this is not terribly difficult, but I'm a bit lost in terms of what I need to do / what various classes / objects / methods / etc. I need to use to do this.So I'm looking for pointers / guidance - code is welcome, but at this point the pointers / guidance would be a significant step forward for me.
Code SnippetsAbridged version of the code thus far (trying to limit it to just the relevant pieces)
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate {
//... various #IBOutlet's for text fields, buttons, etc. ...
#IBOutlet weak var map: MKMapView!
var coords: CLLocationCoordinate2D?
var locationManager: CLLocationManager = CLLocationManager()
var myLocation: CLLocation!
var annotation: MKPointAnnotation!
var annotationList: [MKPointAnnotation] = []
var matchingItems: [MKMapItem] = [MKMapItem]()
override func viewDidLoad() {
super.viewDidLoad()
//... text field delegates, and other initilizations ...
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.delegate = self
}
myLocation = nil
//... other initializations...
}
// Search for things that match what my app is looking for ("<search string>")
func performSearch() {
annotationList.removeAll() // clear list
matchingItems.removeAll() // clear list
var closest = MKMapItem()
var distance = 10000.0
let request = MKLocalSearchRequest()
let span = MKCoordinateSpan(latitudeDelta: 0.001, longitudeDelta: 0.001)
request.naturalLanguageQuery = "<search string>"
request.region = MKCoordinateRegionMake(myLocation.coordinate, span)
let search = MKLocalSearch(request: request)
if search.isSearching {
search.cancel()
}
search.start(completionHandler: {
(_ response, _ error) in
if error != nil {
self.showAlert(msg: "Error occurred in search\nERROR: \(error?.localizedDescription)")
}
else if response!.mapItems.count == 0 {
self.showAlert(msg: "No matches found")
}
else {
for item in response!.mapItems {
// Track the closest placemark to our current [specified] location
let (distanceBetween, prettyDistance) = self.getDistance(loc1: self.myLocation, loc2: item.placemark.location!)
let addrObj = self.getAddress(placemark: item.placemark)
//... some code omitted ...
// Add markers for all the matches found
self.matchingItems.append(item as MKMapItem)
let annotation = MKPointAnnotation()
annotation.coordinate = item.placemark.coordinate
annotation.title = item.name
annotation.subtitle = "\(addrObj.address!) (\(prettyDistance))"
self.map.addAnnotation(annotation)
self.annotationList.append(annotation)
}
//... some code omitted ...
}
})
}
//... code for getDistance(), getAddress() omitted for brevity - they work as designed ...
//... other code omitted as not being relevant to the topic at hand
}
I imagine that I will need to override touchesEnded and possibly touchesBegan and maybe touchesMoved in order to detect the tap.
What I cannot figure out is how to compare a touch's location (represented as X/Y coordinates on the screen) to an MKPointAnnotation's or MKMapItem's location (which is represented as latitude/longitude coordinates on a map)
So - that's kind of where I'm currently stuck. I searched various terms on the web but wasn't able to find anything that [simplisticly] answerwed my question - and in Swift code format (there were a number of postings that looked like they might help, but the code presented wasn't in Swift and I don't do the translation that easily).
UPDATE (19:48 ET)
I found this article: How do I implement the UITapGestureRecognizer into my application and tried to follow it, but ...
I modified the code a bit (added UIGestureRecognizerDelegate):
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) //<<<== See notes below
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
print("A")//#=#
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
print("B")//#=#
}
func handleTap(tap: UITapGestureRecognizer) {
print("ARRIVED")//#=#
let here = tap.location(in: map)
print("I AM HERE: \(here)")//#=#
}
//...
}
With regard to the declaration / definition of tapHandler, I tried the following:
let tapHandler = UITapGestureRecognizer(target: self, action: "handleTap:")
let tapHandler = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
let tapHandler = UITapGestureRecognizer(target: self, action: Selector(("handleTap:"))) // supresses warning
The first two caused a warning to show up in Xcode, the last simply supresses the warning:
[W] No method declared with Objective-C selector 'handleTap:'
When I run my app and tap on a pin - I get the following in my log:
A
B
libc++abi.dylib: terminating with uncaught exception of type NSException
Which would seem (to me) to indicate that the general setup in viewDidLoad is okay, but as soon as it tries to handle the tap, it dies without ever getting to my handleTap function - and thus the warning (shown above) would seem to be far more serious.
So, I'm not sure if I can count this as making progress, but I'm trying...
Thanks to this MKAnnotationView and tap detection I was able to find a solution. My code changes from those originally posted:
class NewLocationViewController: UIViewController, CLLocationManagerDelegate, UITextFieldDelegate, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//...other code...
let tapHandler = UITapGestureRecognizer() //<<<== No parameters
tapHandler.numberOfTapsRequired = 1
tapHandler.numberOfTouchesRequired = 1
tapHandler.delegate = self
map.addGestureRecognizer(tapHandler)
map.isUserInteractionEnabled = true
}
// Not sure who calls this and requires the Bool response, but it seems to work...
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return self.handleTap(touch: touch).count > 0
}
// Major Changes
private func handleTap(touch: UITouch) -> [MKAnnotationView] {
var tappedAnnotations: [MKAnnotationView] = []
for annotation in self.map.annotations {
if let annotationView: MKAnnotationView = self.map.view(for: annotation) {
let annotationPoint = touch.location(in: annotationView)
if annotationView.bounds.contains(annotationPoint) {
self.name.text = annotationView.annotation?.title!
let addr = AddrInfo(composite: ((annotationView.annotation?.subtitle)!)!)
self.address.text = addr.street!
self.city.text = addr.city!
self.state.text = addr.state!
self.zipcode.text = addr.zip!
tappedAnnotations.append(annotationView)
break
}
}
}
return tappedAnnotations
}
//...
}
The AddrInfo piece is my own little subclass that, among other things, takes a string like "1000 Main St., Pittsburgh, PA 15212, United States" and breaks it into the individual pieces so that they can be accessed, well, individually (as indicated in the code above).
There might be an easier, or better, way to achieve what I was looking for - but the above does achieve it, and so I consider it to be the answer for my issue.

didTapInfoWindowOfMarker on iOS App Swift

Right now my app has multiple markers on different locations. If you tap on a marker, there is a small window popping out including a title and a snippet. I would like to implement a button in the window, or make the info window tappable, so it works as a button to execute a function. So I wrote this block in my GoogleMapsViewController.swift:
func mapView(mapView: GMSMapView, didTapInfoWindowOfMarker marker: GMSMarker) {
print("test")
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("jobDetailVC") as! JobDetailViewController
if let value = marker.userData as? PFObject {
vc.name = value.objectForKey("name") as? String
vc.descriptionF = value.objectForKey("description") as? String
vc.price = value.objectForKey("price") as? Double
vc.objectId = value.objectId!
}
}
The reason why I am using: didTapInfoWindowOfMarker is because I wasn't sure how to implement it, so I read the docu from Google Maps: https://developers.google.com/maps/documentation/ios-sdk/reference/protocol_g_m_s_map_view_delegate-p?hl=es and thought that this was the best choice.
Has anyone successfully implemented this, or something similar? Thanks in advance for the help!
You are right in using didTapInfoWindowOfMarker function to add an event in your InfoWindow.
When you're adding Map, add:
mapView_.delegate=self;
Then use this to add the event/function of infoWindow when clicked:
-(void)mapView:(GMSMapView *)mapView
didTapInfoWindowOfMarker:(id<GMSMarker>)marker{
//Info window function
}
Example on GitHub:
// when user tap the info window of store marker, show the product list
func mapView(mapView: GMSMapView!, didTapInfoWindowOfMarker marker: GMSMarker!) {
let storeMarker = marker as StoreMarker
performSegueWithIdentifier("productMenu", sender: storeMarker.store)
}
// when user tap the info window of store marker, pass selected store to the product list controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let controller = segue.destinationViewController as ProductMenuController
controller.store = sender as Store
}
func mapView(mapView: GMSMapView, didTapInfoWindowOfMarker marker: GMSMarker) {
for location in locations {
let pollution = location[0]
if pollution.locationdesc == marker.title {
performSegueWithIdentifier(segueIdentifiers.locations, sender: location)
break
}
}
}
Check this related question:
Adding Click Event on InfoWindow/Marker in Google Maps SDK for native iOS/objective C

Inconsistent results in simulator (iOS)?

I'm building a basic geofence app that allows users to create geofences, view them on a MKMapView, and activate and deactivate them. It is based on the Ray Wenderlich tutorial, but I have adapted it in several ways. Namely, I am using Realm to persist data and I have created a separate LocationHandler class that acts as LocationManagerDelegate and holds a LocationManager. Generally, I tried to move some functions out of viewControllers and into separate classes.
Everything seems to work, except periodically map annotations and overlay aren't rendered correctly in the simulator. About 20% of the time annotations and overlays won't be removed when they should be. Or, colors won't change as they should. Or, a circular overlay will change colors, but the associated pin won't.
Is this due to some error in my code, or is this an artifact of using a simulator?
Thank you for your help
Edit to add some code:
In view controller
//Clicking the 'x' deletes the geofence
func mapView(mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
let anAnnotation = view.annotation as! GeofenceAnnotation
let geofence = anAnnotation.geofence
//stop monitoring geofence
locationManager.stopMonitoringGeofence(geofence!)
//remove representation of geofence from map
removeGeofenceRadiusCircle((geofence?.identifier)!)
mapView.removeAnnotation(anAnnotation)
//delete geofence from realm
try! realm.write {
realm.delete(geofence!)
}
updateGeofenceCount()
}
//Go through all overlays and remove appropriate one
func removeGeofenceRadiusCircle(id: String) {
self.mapView.delegate = self
if let overlays = mapView?.overlays {
for ol in overlays {
if let circleOverlay = ol as? GeofenceRadiusCircle {
let aId = circleOverlay.id
if aId == id {
mapView?.removeOverlay(circleOverlay)
break
}
}
}
}
}
subclass of MKAnnotation
class GeofenceAnnotation: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
var geofence: Geofence?
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String, geofence: Geofence? = nil) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
self.geofence = geofence
}
subclass of MKCircle
class GeofenceRadiusCircle: MKCircle{
var geofence: Geofence?
var color: UIColor?
var id: String = ""
}
It seems like it was a little bit of an error on my side and also maybe an error with the simulator. I needed to remove the old overlay before redrawing in viewWillAppear to account. That seemed to solve the overlay and annotation problems. I also had a problem with the user location not showing all the time in the mapView and that doesn't seem to be the case when I run the app on my phone.

Resources