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.
Related
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)
}
}
I have an app that allows people to put annotations on a map that get stored in a Firebase database, I have recently been trying to add a new feature to the app so to avoid messing about with my real database I created another app in firebase and used it for me to do my testing on. I deleted the GoogleService-Info.Plist and imported the one for the new test app but now when I delete the test GoogleService-Info.Plist and replace it with the one that works with my Live app none of my annotations show up on the map, but any I create from that point onwards will show up.
I've done a clean and build but still the same result. All the data is still there in my original database as my AppStore version of the app works exactly as it should, I really don't know what could be wrong here, I'll let you see what my code is for retrieving the annotations, in case that helps you with your answer.
import UIKit
import MapKit
import Firebase
class MapViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
#IBOutlet weak var mapView: MKMapView!
#IBOutlet weak var postboxesLoggedLabel: UILabel!
var locationManager = CLLocationManager()
let annotation = MKPointAnnotation()
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
displayAnnotations()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation: CLLocation = locations[0]
let latitude = userLocation.coordinate.latitude
let longitude = userLocation.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let lonDelta: CLLocationDegrees = 0.05
let span = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
let region = MKCoordinateRegion(center: location, span: span)
self.mapView.setRegion(region, animated: true)
locationManager.stopUpdatingLocation()
}
func displayAnnotations() {
let ref = Database.database().reference()
ref.child("Postbox").observe(.childAdded, with: { (snapshot) in
let monToFri = (snapshot.value as AnyObject!)!["Monday to Friday Collection Time"] as! String!
let sat = (snapshot.value as AnyObject!)!["Saturday Collection Time"] as! String!
let latitude = (snapshot.value as AnyObject!)!["Latitude"] as! String!
let longitude = (snapshot.value as AnyObject!)!["Longitude"] as! String!
let lastCollection = "Mon - Fri: \(monToFri!)" + " Sat: \(sat!)"
self.annotation.coordinate = CLLocationCoordinate2D(latitude: (Double(latitude!))!, longitude: (Double(longitude!))!)
self.annotation.title = "Last Collection:"
self.annotation.subtitle = lastCollection
self.mapView.addAnnotation(self.annotation)
self.postboxesLoggedLabel.text = String(self.mapView.annotations.count)
})
}
/* func removeAnnotation(gesture: UIGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.ended {
self.mapView.removeAnnotation(annotation)
print("Annotation Removed")
}
} */
#IBAction func myLocation(_ sender: Any) {
locationManager.startUpdatingLocation()
}
#IBAction func mapType(_ sender: Any) {
switch ((sender as AnyObject).selectedSegmentIndex) {
case 0:
mapView.mapType = .standard
case 1:
mapView.mapType = .satellite
default: // or case 2
mapView.mapType = .hybrid
}
}
}
I am quite new to swift and IOS development.
I would like to ask experienced members what is the correct way of creating mapvie and tableview in one viewcontroller and populating them.
The logic of application is the following:
get current location of user
read plist file with POI coordinates
for each POI run a function wich calculates distance between user and point
populate table with data from plist file plus newly calculated data.
Both mapview and tableview are in the same viewcontroller.
in viewDidLoad I am getting users location.
in viewwillappear I am running functions to read plist file and calculate distances between POIs and user.
Everything is working but it is not stable ... sometimes it might show user's location but table will be empty. So I doubt that I am doing everything correctly. Also probably it is not correct to put both map and table inside one class?
Update
Here is the code:
import UIKit
import MapKit
import CoreLocation
class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var theMapView: MKMapView!
#IBOutlet weak var tableView: UITableView!
var locationManager: CLLocationManager!
var branches = [Branch]()
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
tableView.dataSource = self
tableView.delegate = self
locationManager.startUpdatingLocation()
locationManager.startUpdatingHeading()
//mapview setup to show user location
theMapView.delegate = self
theMapView.showsUserLocation = true
theMapView.mapType = MKMapType(rawValue: 0)!
theMapView.userTrackingMode = MKUserTrackingMode(rawValue: 2)!
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
readFromPlist()
}
//MARK: UITableView methods
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return branches.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: MapCustomCell = tableView.dequeueReusableCellWithIdentifier("mapCell") as! MapCustomCell
let brnch = branches[indexPath.row]
cell.mapSetupCell(brnch.cityName, AddressLabel: brnch.address, DistanceLabel: brnch.distance)
return cell
}
//MARK: FUNC TO CALCULATE DISTANCE
func calculateDistance (lat1 lat1: Double, lon1: Double, lat2: Double, lon2: Double) -> String {
return "100km"
}
func locationManager (manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let myCoordinates = locations.last
let myLat = myCoordinates!.coordinate.latitude
let myLong = myCoordinates!.coordinate.longitude
let myCoordinates2D = CLLocationCoordinate2DMake(myLat, myLong)
let myLatDelta = 0.10
let myLongDelta = 0.10
let mySpan = MKCoordinateSpanMake(myLatDelta, myLongDelta)
let myRegion = MKCoordinateRegion(center: myCoordinates2D, span: mySpan)
theMapView.setRegion(myRegion, animated: true)
let myAnno = MKPointAnnotation()
myAnno.coordinate = myCoordinates2D
self.locationManager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("error:" + error.localizedDescription)
}
func readFromPlist() {
//read plist file to extract banks coordinates
let path = NSBundle.mainBundle().pathForResource("poi", ofType: "plist")
let POIarrays = NSArray(contentsOfFile: path!)
for arr in POIarrays! {
var ctName : String!
var brnchAddress : String!
var wrkngHours : String!
var lat : Double!
var long : Double!
ctName = arr.objectForKey("cityName")! as! String
brnchAddress = arr.objectForKey("address")! as! String
wrkngHours = arr.objectForKey("workingHours")! as! String
lat = Double(arr.objectForKey("latitude")! as! String)
long = Double(arr.objectForKey("longitude")! as! String)
let latitude: CLLocationDegrees = lat
let longitude : CLLocationDegrees = long
let bankLocation : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let annotation = MKPointAnnotation()
annotation.coordinate = bankLocation
annotation.title = bnkName
annotation.subtitle = brnchAddress
self.theMapView.addAnnotation(annotation)
let myLatitude = self.locationManager.location?.coordinate.latitude
let myLongitude = self.locationManager.location?.coordinate.longitude
if myLatitude != nil {
let dist = calculateDistance(lat1: latitude, lon1: longitude, lat2: myLatitude!, lon2: myLongitude!)
let b = Branch(cityName: ctName!, address: brnchAddress!, distance: dist)
branches.append(b)
}
}
}
}
Sometimes I got an error "error:The operation couldn’t be completed. (kCLErrorDomain error 0.)" and current location doesn't appear on my map.
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
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.)