My Annotation View is not getting set proper in iphone 7 - ios

I have older application, I'm updating its code to support IOS 10. Its working fine at my end however my client found that the annotation is not display in center of the screen in his iPhone 7, So annotation's view is getting cut from left side. Its working fine in all the devices I have(upto iPhone 6) and in simulators with iPhone 7.
Any suggestion where I suppose to look.
Thank you.

I had a similar problem.
I assigned the values ​​of a class in a view that is displayed on the screen. However, on the iPhone 7 I used for testing, the didSelect delegate function was not being accessed. But in my iPhone 11 it worked.
I had this in my code:
func mapView(_ mapView: MKMapView, didSelect annotation: MKAnnotation) {
if let sucursalSeleccionada = annotation as? StoreAnnotation{
if self.stores.isEmpty{return}
lblSucursal.text = sucursalSeleccionada.title
lblDireccion.text = sucursalSeleccionada.subtitle
let idTiendaSeleccionada = sucursalSeleccionada.idTienda
let tiendaSeleccionada = self.stores.first(where: {$0.id == idTiendaSeleccionada})
self.sucursal = tiendaSeleccionada
self.seleccionoSucursal = true
self.sucursalSeleccionada = tiendaSeleccionada
self.nombreDeTienda = sucursalSeleccionada.title ?? ""
viewTienda.isHidden = false
}
}
My solution was to change the MKAnnotation type parameter to MKAnnotationView and when ensuring the parameter use its .annotation property.
I hope it works for you, greetings.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
if let sucursalSeleccionada = view.annotation as? StoreAnnotation{
if self.stores.isEmpty{return}
lblSucursal.text = sucursalSeleccionada.title
lblDireccion.text = sucursalSeleccionada.subtitle
let idTiendaSeleccionada = sucursalSeleccionada.idTienda
let tiendaSeleccionada = self.stores.first(where: {$0.id == idTiendaSeleccionada})
self.sucursal = tiendaSeleccionada
self.seleccionoSucursal = true
self.sucursalSeleccionada = tiendaSeleccionada
self.nombreDeTienda = sucursalSeleccionada.title ?? ""
viewTienda.isHidden = false
}
}

Related

Crashes app in background when adding annotation on MKMapView

After adding annotation and if user put app in background, after 1 second it crashes. In foreground it is not crashing. It does not show in logs about crash.
Adding a annotation from viewDidLoad() method
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: lat as! Double, longitude: long as! Double)
mapView.addAnnotation(annotation)
Delegate method code:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let pin = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: nil)
pin.canShowCallout = true
let button = UIButton(type: .detailDisclosure)
pin.rightCalloutAccessoryView = button
return pin
}
Crash log:
Please let me know what I am doing wrong.
UPDATE
Even after created a new project with simply adding an annotation in MKMapView. It behaves the same as mine. Is it global framework issue? Any other guys faced/facing?
Please Help!
Using expressions like lat as! Double is almost always a bad idea.
Try something like if let lat = lat as? Double and make a nice error handling if that fails.

How to identify if MKPointAnnotation has been pressed in Swift?

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.

iOS Swift - How to wait end of request before return Info Window (Google Maps)

I am working with Google Maps and custom Info Window.
I have an issue where I need to update a label but the data comes from a success callback.
The problem here is that when the data is available I have already returned my Info Window and I can't update it anymore because it is already rendered (snapshot of the view).
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
let infoWindow = Bundle.main.loadNibNamed("CustomInfoWindow", owner: self, options: nil)?.first as! CustomInfoWindow
let obs = marker.userData as! Observation
ObsService.shared.getCityFromLatLong(lat: String(obs.coordinate.latitude), long: String(obs.coordinate.longitude)) { response in
infoWindow.outletPlaceLabel.text = response
}
infoWindow.setDateAndTime(timestamp: obs.timestamp)
return infoWindow
}
I know I have several options such as :
Notification
Wait until request is done (blocking UI?)
Schedule my task
Init method with logic instead of loadNibNamed only
I begin developing with Swift 3.
EDIT: It's working with a sleep(2). But I CAN'T block the UI every time I need to display an info window.
I can't find an other solution.
var cityName: String?
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
if cityName == nil {
let obs = marker.userData as! Observation
ObsService.shared.getCityFromLatLong(lat: String(obs.coordinate.latitude), long: String(obs.coordinate.longitude) { response in
self.cityName = response
// Replace the existing map view with a new one here. Or call it's refresh function if it has one (I don't know, haven't used Google map view)
return
}
}
return self.mapViewHelper(mapView, markerInfoWindow: marker, city: cityName ?? "")
}
func mapViewHelper(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker, city: String?) -> UIView? {
let infoWindow = Bundle.main.loadNibNamed("CustomInfoWindow", owner: self, options: nil)?.first as! CustomInfoWindow
let obs = marker.userData as! Observation
infoWindow.outletPlaceLabel.text = response
infoWindow.setDateAndTime(timestamp: obs.timestamp)
return infoWindow
}
If you have set the map view initially using Interface builder, you can give it a tag, and then search for the view with the relevant tag, instantiate a new map view where I have written the comment, and replace the existing map view (copying its frame from the existing map view). The map view may also have a refresh function you can call. See the documentation for views for how to search for views with tags (sorry if that's teaching a grandmother to suck eggs!)
Try this :
let infoWindow:CustomInfoWindow? // declare out side of method
Your function Now
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
infoWindow = Bundle.main.loadNibNamed("CustomInfoWindow", owner: self, options: nil)?.first as! CustomInfoWindow
let obs = marker.userData as! Observation
ObsService.shared.getCityFromLatLong(lat: String(obs.coordinate.latitude), long: String(obs.coordinate.longitude)) { response in
infoWindow.outletPlaceLabel.text = response
}
infoWindow.setDateAndTime(timestamp: obs.timestamp)
return infoWindow
}

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.

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