GMS Mapview flickers, reloads and crashes when updating coordinates - ios

I have been stuck on this problem for the last three weeks and can't seem to get past it, it's driving me insane. I believe that I have all the correct code, but it is just not ordered properly. I am currently designing an app somewhat like Uber, but a completely different concept.
What I am trying to do is pull down coordinates from Firebase and then drop two "pins" or markers on a GMSMapview. I have a UIView classed as GMSMapview and wired up via an IBOutlet. So when the ViewController loads the Google Maps MapView is in the UIView. What I am trying to accomplish is having a "pin" where the current "driver" is and a second pin where "my" location is. What I want to accomplish is for the map to "zoom & follow" the driver until he arrives at my location, similar to Uber.
I have tried hundreds of different types of code combinations and found articles here on StackOverflow that I followed, but those did not work. I was able to get the pins to show up (green is driver, red is where he is going) and when I went into Firebase and changed one of the coordinates the screen would "jump" or flicker really bad. Doing some more research I read that in order to accomplish this concept (and to show all the "cars" as markers on a GMS MapView as Uber does, in another ViewController) I need to put my coordinates in an array, and then loop through a model containing a struct with the variables. Once I did that, the "flickering" stopped, but then the whole view controller continued to reload from scratch(as if the ViewController was just opened for the first time) every time I updated a coordinate. Sometimes I did latitude and other times longitude, but it made no difference. Obviously since I was updating Firebase manually, i could not do both values at the same time.
The articles I found on StackOverflow seemed very promising and I believe I am on the right track, except after implementing some of the recommended code from Google and here, I am now getting a nil crash (identified in my code below). This crash is occurring after I go into Firebase and manually update a coordinate (either lat or long).
After almost a month of trying to tweak and get this code to work, I am looking for some guidance as to where I have gone wrong with my code. I am using the latest PODS for GoogleMaps and Firebase. Bottom line, I am looking for the way to move the GMS Marker while the coordinates update live in Firebase, as well as have the map zoom in as it gets closer to "my location".
Here are the articles I researched and followed:
GMS Map View Maker Flicker issue
How do I move marker along with moving of Google Map in iOS?
Here is my code:
import UIKit
import CoreLocation
import CoreData
import Firebase
import FirebaseDatabase
import FirebaseAuth
import GoogleMaps
import GooglePlaces
import GooglePlacesPicker
import Alamofire
import SwiftyJSON
class SOPOST: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate, Alertable {
#IBOutlet weak var connectedMapView: GMSMapView!
#IBOutlet weak var driverInfoView: UIView!
let currentUserId = Auth.auth().currentUser?.uid
var markers = [] as NSArray
var locationManager = CLLocationManager()
var placesClient: GMSPlacesClient!
var zoomLevel: Float = 12.0
var likelyPlaces: [GMSPlace] = []
var selectedPlace: GMSPlace?
var custlat: CLLocationDegrees?
var custlong: CLLocationDegrees?
var driverlat: CLLocationDegrees?
var driverlong: CLLocationDegrees?
var destlat: CLLocationDegrees?
var destlong: CLLocationDegrees?
var location: CLLocation?
var destinationMarker = GMSMarker()
var currentCoordAddress: CLLocationCoordinate2D?
var destinationCoordAddress: CLLocationCoordinate2D?
var driverCoordAddress: CLLocationCoordinate2D?
override func viewDidLoad() {
super.viewDidLoad()
connectedMapView.delegate = self
DispatchQueue.main.async {
DataService.instance.REF_TRIPS.observe(.value, with: { (snapshot) in
if let findDriverSnapshot = snapshot.children.allObjects as? [DataSnapshot] {
for driver in findDriverSnapshot {
if driver.childSnapshot(forPath: "passengerKey").value as? String == self.currentUserId! {
let acceptanceStatus = driver.childSnapshot(forPath: "tripIsAccepted").value as! Bool
if acceptanceStatus == true {
if let observeAcceptDict = driver.value as? Dictionary<String, AnyObject> {
let pickupCoordinateArray = observeAcceptDict["pickupCoordinate"] as! NSArray
self.custlat = pickupCoordinateArray[0] as? CLLocationDegrees
self.custlong = pickupCoordinateArray[1] as? CLLocationDegrees
let driverCoordinateArray = observeAcceptDict["driverCoordinate"] as! NSArray
self.markers = observeAcceptDict["driverCoordinate"] as! NSArray
self.driverlat = driverCoordinateArray[0] as? CLLocationDegrees
self.driverlong = driverCoordinateArray[1] as? CLLocationDegrees
let prepareLocation = CLLocation(latitude: self.driverlat!, longitude: self.driverlong!)
self.location = prepareLocation
let destCoordinateArray = observeAcceptDict["destinationCoordinate"] as! NSArray
self.destlat = destCoordinateArray[0] as? CLLocationDegrees
self.destlong = destCoordinateArray[1] as? CLLocationDegrees
self.currentCoordAddress = CLLocationCoordinate2DMake(self.custlat!, self.custlong!)
self.destinationCoordAddress = CLLocationCoordinate2DMake(self.destlat!, self.destlong!)
self.driverCoordAddress = CLLocationCoordinate2DMake(self.driverlat!, self.driverlong!)
CATransaction.begin()
CATransaction.setAnimationDuration(1.0)
self.destinationMarker.position = CLLocationCoordinate2D(latitude: (self.markers[0] as? CLLocationDegrees)!, longitude: (self.markers[1] as? CLLocationDegrees)!)
self.connectedMapView.camera = GMSCameraPosition.camera(withTarget: self.destinationMarker.position, zoom: 12.0)
self.destinationMarker.icon = GMSMarker.markerImage(with: UIColor.green)
self.destinationMarker.map = self.connectedMapView
self.destinationMarker.tracksViewChanges = false
CATransaction.commit()
let customerdestmarker = GMSMarker()
customerdestmarker.position = CLLocationCoordinate2D(latitude: self.custlat!, longitude: self.custlong!)
customerdestmarker.icon = GMSMarker.markerImage(with: UIColor.red)
customerdestmarker.map = self.connectedMapView
customerdestmarker.tracksViewChanges = false
}
}
}
}
}
})
}
}
func updateLocationoordinates(coordinates:CLLocationCoordinate2D) {
if destinationMarker == nil
{
destinationMarker = GMSMarker()
destinationMarker.position = coordinates
let image = UIImage(named:"destinationmarker")
destinationMarker.icon = image
destinationMarker.map = self.connectedMapView
destinationMarker.appearAnimation = GMSMarkerAnimation.pop
}
else
{
CATransaction.begin()
CATransaction.setAnimationDuration(1.0)
destinationMarker.position = coordinates
CATransaction.commit()
}
}
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
self.destinationMarker = GMSMarker(position: self.location!.coordinate) // <----CRASHES HERE: Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
destinationMarker.position = position.target
let destinationLocation = CLLocation(latitude: destinationMarker.position.latitude, longitude: destinationMarker.position.longitude)
let destinationCoordinate = destinationLocation.coordinate
updateLocationoordinates(coordinates: destinationCoordinate)
}
}

Related

Mapbox Navigation in iOS with in my mapView controller

I want to integrate Mapbox navigation in iOS, I can easily get the direction/route between two coordinate also to get the navigation path from mapbox we can use below code
let options = NavigationOptions(styles: nil)
let viewController = NavigationViewController(for: self.directionsRoute!)
viewController.delegate=self
self.present(viewController, animated: true, completion: nil)
But the problem is I want to display the navigation in my mapview which is a part of another view controller, I can do that by getting a direction/route and instruction but I can't find any method which will be called every second so that I can update route instruction, as well as route, in case of user change the path.
Let me know if I am missing anything or any changes needed.
-Thanks in advance
here is my approach:
first i did get only directions instructions from the MapBox api taking advantage of it's free API calls quota and draw the instructions on GMSMapView or MapKit taking advantage of their good performance and memory management.
podfile
pod 'MapboxDirections.swift'
import MapboxDirections
this is done through the below code
have the property for MapBox directions
#IBOutlet weak var googleMapView: GMSMapView!
let locationManager = CLLocationManager()
let mapBoxirections = Directions(accessToken: osmToken)
var path: GMSMutablePath?
then do the actual api call
private func drawRouteBetween(source: StopModel, destination: StopModel) {
guard let name = source.name, let lat = source.latitude, let lng = source.longitude else { return }
guard let nameDest = destination.name, let latDest = destination.latitude, let lngDest = destination.longitude else { return }
let waypoints = [
Waypoint(coordinate: CLLocationCoordinate2D(latitude: lat, longitude: lng), name: name),
Waypoint(coordinate: CLLocationCoordinate2D(latitude: latDest, longitude: lngDest), name: nameDest),
]
let options = RouteOptions(waypoints: waypoints, profileIdentifier: .automobile)
options.includesSteps = true
options.distanceMeasurementSystem = .metric
mapBoxirections.calculate(options) { (waypoints, routes, error) in
guard error == nil else {
print("Error calculating directions: \(error!)")
return
}
if let route = routes?.first, let leg = route.legs.first {
for step in leg.steps {
if let coordinates = step.coordinates {
for (index, point) in coordinates.enumerated() {
let source = point
if index <= coordinates.count - 2 {
let destination = coordinates[index + 1]
self.drawPolyLine(source: source, destination: destination)
}
}
}
}
}
}
}
note that StopModel is my custom made CLLocation so feel free to replace it with your own as long it has the latitude and longitude
create the method that draws Polyline on your CLLocationManagerDelegate as below
private func drawPolyLine(source: CLLocationCoordinate2D, destination: CLLocationCoordinate2D){
path?.add(source)
path?.add(destination)
let polyLine = GMSPolyline(path: path)
polyLine.strokeWidth = 4 // width of your choice
polyLine.strokeColor = .red // color of your choice
polyLine.map = googleMapView
}
then take a look at the MapBoxDirections.Route model and explore it's properties you will find very useful info inside it
and then take advantage of the callback function from the GMS Delegate that notifies you with the location update instead having a timer and calling it every second this is more efficient way
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
/* do your business here */
}
do not forget to have the delegate of the location manager to self or the class of your choice
Maybe this helps a bit: you can easily add observer for route progress changes:
NotificationCenter.default.addObserver(self,
selector: #selector(progressDidChange(notification:)),
name: .routeControllerProgressDidChange,
object: navigationService.router)
You need a navigation service with your route by creating it like
let navigationService = MapboxNavigationService(route: route)
The function progressDidChange can do something like:
#objc func progressDidChange(notification: NSNotification) {
guard let routeProgress = notification.userInfo?[RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress,
let location = notification.userInfo?[RouteControllerNotificationUserInfoKey.locationKey] as? CLLocation else {
return
}
// you have all information you probably need in routeProgress, f.E.
let secondsRemaining = routeProgress.currentLegProgress.currentStepProgress.durationRemaining
...
}

Thread 1: EXC_BAD_INSTRUCTION

I am making this app where users can see their own location and other users location. I recently just got an error saying
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP,subcode=0x0)
at this line:
var lat = locationManager.location?.coordinate.latitude
I have not managed to fix it.
What is causing it and how can I fix it?
For any who might would like the rest of the code:
import UIKit
import Parse
import CoreLocation
import MapKit
class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var myLocation: [CLLocation] = []
#IBOutlet weak var MapView: MKMapView!
#IBOutlet var UsernameTextField: UITextField!
#IBOutlet var PasswordTF: UITextField!
#IBOutlet var EmailTF: UITextField!
var locationManager: CLLocationManager!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let locationManager = CLLocationManager()
let lat = locationManager.location!.coordinate.latitude
let lon = locationManager.location!.coordinate.longitude
let location = CLLocationCoordinate2D(latitude: lat, longitude: lon)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(location, span)
MapView!.setRegion(region, animated: true)
let anotation = MKPointAnnotation()
anotation.coordinate = location
anotation.title = "My tittle"
anotation.subtitle = "My Subtitle"
MapView!.addAnnotation(anotation)
print("Welcome in MapViewController")
}
}
This is what #matt is talking about:
The problem is that you are asking for the location manager's location
without checking to see whether the result is nil
Here's how you check to see if your value is nil:
option 1:
guard let lat = locationManager.location?.coordinate.latitude else {
return
}
option 2:
if let latCheck = locationManager.location?.coordinate.latitude {
// assign your lat value here
} else {
// handle the problem
}
You need to change your mindset, when you see an ! you should probably be unwrapping your optional value in one of the above two ways.
update based on comments:
You can also try creating a new variable to work with and see how it works:
guard let location = locationManager.location else {
return
}
then:
let lat = location.coordinate.latitude
let lon = location.coordinate.longitude
The problem is that you are asking for the location manager's location without checking to see whether the result is nil. Well, it is (probably because there has not been time to get the actual location yet). Therefore, when you try to get that location's latitude and longitude, lat and lon, they are nil as well. Therefore when you force-unwrap them, you crash, because you cannot unwrap nil.

Map with Photos using Parse

I'm trying to do exactly this using Parse and Swift.
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
var latitude:Double = 0.0
var longitude:Double = 0.0
#IBOutlet var map: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.setNavigationBarHidden(true , animated: true)
self.map.showsUserLocation = true
self.map.delegate = self
PFGeoPoint.geoPointForCurrentLocationInBackground { (geopoint:PFGeoPoint?, error:NSError?) -> Void in
var query = PFQuery(className: "Locations")
query.whereKey("geopoint", nearGeoPoint: geopoint!)
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if let myObjects = objects {
for object in myObjects {
var thePoint: PFGeoPoint = object["geopoint"] as! PFGeoPoint
self.latitude = thePoint.latitude
self.longitude = thePoint.longitude
NSLog(" Hej %f, %f", self.latitude, self.longitude)
var annotationCoordinate: CLLocationCoordinate2D = CLLocationCoordinate2DMake(self.latitude, self.longitude)
var annotation = MKPointAnnotation()
annotation.coordinate = annotationCoordinate
annotation.title = object["discovery"] as! String
annotation.subtitle = object["location"] as! String
self.map.addAnnotation(annotation)
}
}
}
}
}
Big picture though, I'm trying to create a way to access pictures stored on Parse (and their locations) and display them on a map if they meet my query. I greatly appreciate any help!
if you are getting the conversion error in
var thePoint: PFGeoPoint = object["geopoint"]
change it to
var thePoint: PFGeoPoint = object["geopoint"] as! PFGeoPoint
Regarding converting Double to Int, it appears to be as you found but I don't know the rationale behind the decision by the designers of swift. I would like to understand why you need the conversion since there may be an alternate implementation

In Swift, can I detect if a CLLocation is valid or not?

I am passing a location to another UIViewController which has a MapView using prepareForSegue. I call this receivedLocation. The map centers on the location that is passed. If the user clicks a button to take them to the map without first sending a location, won't this fail?
How can I test to see if receivedLocation actually contains data, so that I can set the map to center on something else if it's empty?
Would creating a Boolean variable, and initially setting it false but turning it to true when passing a location and testing that work as I desire?
I see something called CLLocationCoordinateIsValid is available, but its parameter is a 2DCoordinate, where I run into the same problem of testing whether or not receivedLocation.
I'm fairly new at this and tried looking for an answer already but couldn't find an appropriate one. Thank you!
import UIKit
import MapKit
import CoreLocation
class mapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {
#IBOutlet var map: MKMapView!
#IBOutlet var notes: UITextView!
var receivedLocation = CLLocation()
var annotation = MKPointAnnotation()
var currentManager:CLLocationManager!
var latitute:CLLocationDegrees = CLLocationDegrees()
var longitude:CLLocationDegrees = CLLocationDegrees()
override func viewDidLoad() {
super.viewDidLoad()
map.layer.cornerRadius = 12.0
notes.layer.cornerRadius = 12.0
currentManager = CLLocationManager()
currentManager.delegate = self
currentManager.desiredAccuracy = kCLLocationAccuracyBest
currentManager.startUpdatingLocation()
if CLLocationCoordinate2DIsValid(receivedLocation.coordinate) {
latitute = receivedLocation.coordinate.latitude
longitude = receivedLocation.coordinate.longitude
annotation.coordinate.latitude = receivedLocation.coordinate.latitude
annotation.coordinate.longitude = receivedLocation.coordinate.longitude
map.addAnnotation(annotation)
} else {
latitute = currentManager.location.coordinate.latitude
longitude = currentManager.location.coordinate.longitude
}
var latDelta: CLLocationDegrees = 0.01
var lonDelta: CLLocationDegrees = 0.01
var span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)
var location : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitute, longitude)
var region: MKCoordinateRegion = MKCoordinateRegionMake(location, span)
map.setRegion(region, animated: false)
}
So far this is not working for me. CLLocationCoordinate2DIsValid always returns true and centers around an island, as well as adds an annotation to that weird spot.
First of all, do not needlessly put a useless CLLocation() into receivedLocation. Declare receivedLocation as an implicitly unwrapped Optional:
var receivedLocation : CLLocation! = nil
That way, either someone has set it or they have not. If they have not, it is nil, and you can test that:
if receivedLocation != nil {
// okay, it's a real location!
}
Second, your else is never going to work, because it takes time for the sensors to warm up and get a location - in fact, they may never get one. So I can pretty well guarantee that in the case where receivedLocation is nil, currentManager.location will not be any use either. (See my answer here for an explanation, and pointer to sample code, of how to use a location manager to get a single location value.)

Swift Mapkit example project

I am new to iOS environment . I have created a map kit app for learning . I added delegate to viewController and also did reference outlet but simulation screen still blank here is my code what is wrong ?
import Foundation
import MapKit
class Places{
var latitute : CLLocationDegrees
var longtitut : CLLocationDegrees
var placeLoc : CLLocationCoordinate2D?
var theAnnotation : MKPointAnnotation?
init (latit : CLLocationDegrees, long :
CLLocationDegrees, mytitle : String ,mysubTitle : String){
self.latitute = latit
self.longtitut = long
setLocation()
setAnnotations(mytitle, subtitle: mysubTitle)
}
func setLocation ( ) {
placeLoc = CLLocationCoordinate2DMake(self.latitute, self.longtitut)
}
func setAnnotations (title : String , subtitle : String ) {
theAnnotation = MKPointAnnotation()
theAnnotation?.coordinate = placeLoc!
theAnnotation?.title = title
theAnnotation?.subtitle = subtitle
}}
ViewController
import UIKit
import MapKit
class ViewController: UIViewController,MKMapViewDelegate {
#IBOutlet weak var myMapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
var latDelta : CLLocationDegrees
var longDelta : CLLocationDegrees
longDelta = 0.01
latDelta = 0.01
var theSpan : MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
var place1 : Places = Places(latit: 45.995079, long: 54.121006, mytitle: "MyHome", mysubTitle: "Home Sweet Home")
var theRegion : MKCoordinateRegion = MKCoordinateRegionMake(place1.placeLoc!, theSpan)
self.myMapView.setRegion(theRegion, animated: true)
self.myMapView.addAnnotation(place1.theAnnotation)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}}
Take the constraints off to start with and re-run the application, you should see the map appear. Then use the Pin button in the design area to set the constraints. If you want it to fill the view set each vales to 0 and enable the I bar adjacent to each value.
Remember that Xcode 6 is in beta and can be a bit flakey. Also, you might want to confirm its your constraints, go to Debug > View Debugging > Capture View Hierarchy and turn the wireframe on to see where your view is ending up.
Final tip, turn on the assistant editor and change it to preview the storyboard so that you can see how the constraints are impacting on the map view.
Use this below code in vieDidLoad method,no need to create another swift file.It will display annotation with map view.Maybe it will helpful you
var latDelta : CLLocationDegrees
var longDelta : CLLocationDegrees
latDelta = 0.01
longDelta = 0.01
let theSpan:MKCoordinateSpan = MKCoordinateSpanMake(latDelta , longDelta)
let location:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 13.068452, longitude: 80.25812)
let theRegion:MKCoordinateRegion = MKCoordinateRegionMake(location, theSpan)
mapview.setRegion(theRegion, animated: true)
var anotation = MKPointAnnotation()
anotation.coordinate = location
mapview.addAnnotation(anotation)

Resources