Crashes app in background when adding annotation on MKMapView - ios

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.

Related

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.

Reusing iOS MapKit MKPointAnnotation object

I'm using this function to change the location of the map marker based on user selection:
let annotation = MKPointAnnotation() //global reused annotation object
func setPin(mapView: MKMapView, longitude: Double, latitude: Double, title: String) {
annotation.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
annotation.title = title
mapView.addAnnotation(annotation)
}
The coordinates and title changes repeatedly, so I'm a little concerned if this is the correct approach. The MKPointAnnotation object is instantiated only once, as a global, and only its contents are updated when the setPin() function is called. So far, it's been working without issue, besides the glitch with the simulator not refreshing/rendering the title sometimes.
Would doing this cause any leaks? Am I missing any steps to free the obejct or remove it from the map before reusing it, perhaps?
TIA.

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
}

My Annotation View is not getting set proper in iphone 7

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

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