Swift 2 - Uber-like location picker with MKMapView - ios

I'm trying to make an uber-like location picker from a small MKMapView in my class. My problem is the following :
Since I want to have the initial region to be centered at the user current location, I set the initial region inside the location manager function like this:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let locValue:CLLocationCoordinate2D = (locations.last?.coordinate)!
let latDelta = 0.005
let lonDelta = 0.005
let span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: latDelta, longitudeDelta: lonDelta)
let region:MKCoordinateRegion = MKCoordinateRegionMake(locValue, span)
self.locationMap.setRegion(region, animated: false)
}
The problem with this is that every time that the user tries to drag the map region to pick a location, obviously since the location is being always updated and the function is being called at everytime the location is updates, the map region goes back to its initial point as the function above states.
Another thing is that if I try to extract the locValue variable and assign it to a instance variable of the class within the function adding a line like this :
self.userLocation = locValue
self.userLocation won't change. If you have any suggestion on how to get a single location without updating it or how to fix either one of these problems it would be really appreciated if you could let me know.
Thanks.

Related

How to navigate vehicle icon for current location using google map SDK in swift?

I'm calling direction API to draw the route and trying to move the current location marker icon as user moves the location, But I m facing following problems while implementation.
while moving the icon, It appears like icon got tilt.
It doesn't display the actual user position on the route, Icon appears like it is traveling on the side of route.
My location icon (blue icon) on google map continuously changes its position.
func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
let direction = newHeading.trueHeading
lastDriverAngleFromNorth = direction
self.sourceMarker?.rotation = (lastDriverAngleFromNorth - mapBearing) - bearingValue
DispatchQueue.main.async {
CATransaction.begin()
CATransaction.setValue(2, forKey: kCATransactionAnimationDuration)
self.gmsmapView?.animate(toBearing: newHeading.magneticHeading)
CATransaction.commit()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let lastLocation = locations.last {
self.currentLocation = lastLocation
let zoom = self.gmsmapView?.camera.zoom ?? 20.0
zoomLevel = zoom
let destination = CLLocation.init(latitude: viewModel.marker.location[0], longitude: viewModel.marker.location[1])
let bearing = getBearingBetweenTwoPoints(point1: lastLocation.coordinate, point2: destination.coordinate)
self.cameraMoveToLocation(toLocation: lastLocation, zoom:zoom, bearing: bearing)
}
}
func mapView(_ mapView: GMSMapView, didChange position: GMSCameraPosition) {
mapBearing = position.bearing
if let coordinate = self.currentLocation?.coordinate{
self.rerouteCalculation(currentLocation:coordinate)
}
self.sourceMarker?.rotation = (lastDriverAngleFromNorth - mapBearing) - bearingValue
}
func cameraMoveToLocation(toLocation: CLLocation, zoom : Float, bearing : Double) {
self.gmsmapView?.animate(toLocation: toLocation.coordinate)
self.sourceMarker?.position = toLocation.coordinate
}
Can anyone please help me, I'm stuck here.
Make sure that you choose the correct location accuracy for your needs.
CCLocationAccuracy offers the following options:
kCLLocationAccuracyBestForNavigation: <= You should use this when the device is plugged to a power source
The highest possible accuracy that uses additional sensor data to
facilitate navigation apps.
kCLLocationAccuracyBest: <= You should use this when the device runs on battery
The best level of accuracy available.
kCLLocationAccuracyNearestTenMeters: Accurate
to within ten meters of the desired target.
kCLLocationAccuracyHundredMeters: Accurate to
within one hundred meters.
kCLLocationAccuracyKilometer: Accurate to the
nearest kilometer.
kCLLocationAccuracyThreeKilometers: Accurate
to the nearest three kilometers.

why CoreLocation unable to fetch new GPS data and Mapkit display this old GPS data

I am working on MapKit and core location to display my current GPS location.
The code is working ok but with some problems.
The problem is as follows:
I turn on my app in an open area like on the roadside. The app is able to get my current GPS location and display it on the Map.
I walk into a building with the app on. When inside, I launch the page to show the GPS location. It shows my previous GPS location. As I know, when I am inside the building, the Mapkit and Corelocation should not be able to get GPS. But in this case, it shows my previous GPS data!
I walk out of the building with the app on. In the open space I launch the page to show my current GPS location but the app is unable to get my new GPS location but displays the previous GPS data. In this case the app should fetch a new GPS location. I have to try a few times to launch the page (Navigate from GPS-VC to home-VC, from home click a button to launch the GPS-VC to get GPS).
Why is the GPS so slow even though I am in the open space with good signal strength?
Is there a difference to call these methods:
LocationManager.startUpdatingLocation()
LocationManager.requestLocation()
Here the code:
#IBOutlet weak var Map: MKMapView!
var locationMgr : CLLocationManager!
override func viewDidload(){
if(CLLocationManager.locationServicesEnabled() )
{
locationMgr = CLLocationManager()
locationMgr.delegate = self
locationMgr.desriedAccuracy = kCLLocationAccuracyBest
locationMgr.requestWhenInuseAuthorization()
locationMgr.startUpdatingLocation()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLoction: CLLocation = locations[0]
let latitude = userLoction.coordinate.latitude
let longitude = userLoction.coordinate.longitude
let latDelta: CLLocationDegrees = 0.05
let lonDelta: CLLocationDegrees = 0.05
let span:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, lonDelta)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
let region: MKCoordinateRegion = MKCoordinateRegionMake(location, span)
let dropin = MKPointAnnotation()
dropin.coordinate = location
droping.title = "Here"
self.Map.setRegion(region, animated: true)
self.Map.addAnnotation(dropin)
self.Map.showsUserLocation = true
}
I'm not sure this will solve all 3 of your issues but I spotted the following issues in your delegate method:
the locations array is sorted in ascending time order so locations.last will contain the most recent location.
This method might be called with old cached values so you need to check the timestamp of the location to decide if it's worth using.

I can't figure out how to activate this function

I am making an app with a function as shown here:
func RMC(_ manager: CLLocationManager, locations: [CLLocation]) {
let manager = CLLocationManager()
let location = locations[0]
let span:MKCoordinateSpan = MKCoordinateSpanMake(0.001, 0.001)
let myLocation:CLLocationCoordinate2D = CLLocationCoordinate2DMake(location.coordinate.latitude, location.coordinate.longitude)
let region:MKCoordinateRegion = MKCoordinateRegionMake(myLocation, span)
let annotation = MKPointAnnotation()
annotation.coordinate = location.coordinate
mapView.setRegion(region, animated: true)
self.mapView.addAnnotation(annotation)
annotation.title = "My Car"
And I want to activate it in a #IBA function with this code:
RMC(CLLocationManager, locations: [CLLocation])
It comes up with this error:
Cannot convert value of type 'CLLocationManager.Type' to expected argument type 'CLLocationManager'
Please help!
You need to instantiate a CLLocationManager object and pass it in. At your call site right now, you're just referring to the type CLLocationManager instead of passing in an instance of CLLocationManager. You're doing the same in the array of CLLocations, but you generally don't instantiate those yourself. You'll have to change the call to:
RMC(CLLocationManager(), locations: [])
Also, the first line line in your function where you create a new CLLocationManager is redundant and should be removed, as it makes it impossible to use the CLLocationManater object that was passed in (it may be a compiler error, but I haven't tested to make sure)

Zoom in on user location in iOS

I've been trying to solve this for a few hours and I can't seem to arrive at a solution. I am trying to create a button on my map view that zooms in on the users location when pressed. Here is the code for the function that pertains to the button:
func zoomInOnLocation() {
let userLocation = MKUserLocation()
let locationManager = CLLocationManager()
locationManager.requestWhenInUseAuthorization()
let currentLocation: CLLocation? = userLocation.location
let latitude = currentLocation?.coordinate.latitude
let longitude = currentLocation?.coordinate.longitude
let span: MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude!, longitude!)
let region: MKCoordinateRegion = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: true)
}
When I click the button in the simulator, I receive an error stating fatal error:
unexpectedly found nil while unwrapping an Optional value
with the fifth line mapDelegate.mapView!... highlighted in red. Also, I added the proper tag to Info.plist. Any help is much appreciated.
Check this :
if #available(iOS 9.0, *) {
locationManager.requestLocation()
} else {
// Fallback
}
let latitude:CLLocationDegrees = //insert latitutde
let longitude:CLLocationDegrees = //insert longitude
let latDelta:CLLocationDegrees = 0.05
let lonDelta:CLLocationDegrees = 0.05
let span = MKCoordinateSpanMake(latDelta, lonDelta)
let location = CLLocationCoordinate2DMake(latitude, longitude)
let region = MKCoordinateRegionMake(location, span)
mapView.setRegion(region, animated: false)
For more : Making the map zoom to user location and annotation (swift 2)
So you probably do not want to to interact with the mapView delegate as you are doing right now.
How about adding the delegates to the class of the view that holds the mapView, like this:
class ViewController: UIViewController, CLLocationManagerDelegate, MKMapViewDelegate {
and setting the delegates in viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
locationManager.delegate = self
mapView.delegate = self
}
And of course you want to have the locationManager and userLocation set up:
let locationManager = CLLocationManager()
var userLocation = CLLocation()
Notice that userLocation is a variable because most likely you would want to update it at some point.
This is how you probably want to work with locationManager:
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
You would make changes according to the needs of your app, and taking into account that the desired accuracy has an impact on battery life. Also, are you starting and stopping updates on userLocation? Because I do not see that in your code, unless you are doing it outside of this function.
A good practice is to try to minimize what an specific function does down to one task. You probably want to do all this setup elsewhere and then only zoom in inside the function. :)
Finally, in order to zoom in, you can change the values of MKCoordinateSpanMake, and remember that larger span values zoom in the map, so a smaller area is viewable.
let userLocationCoordinates = CLLocationCoordinate2D(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.3, 0.3)
let region = MKCoordinateRegion(center: userLocationCoordinates, span: span)
mapView.setRegion(region, animated: true)
Hopefully that helps you out a bit, let me know how it goes!
Perhaps this will help. I created this function to zoom into an area defined by an array of positions, ranging from just the current user location out to the area around a set of points making up a polyline. The function provides for a buffer around the points based on a regionPaddingFactor constant set in my system constants.
func setRectView(_ locations: [MKAnnotation], mapView: MKMapView) // Size the area for display and reset the view
{
var maxLat = -90.0
var minLat = 90.0
var maxLon = -180.0
var minLon = 180.0
if locations.count >= 1 {
for waypoint in locations {
maxLat = max(maxLat, waypoint.coordinate.latitude)
minLat = min(minLat, waypoint.coordinate.latitude)
maxLon = max(maxLon, waypoint.coordinate.longitude)
minLon = min(minLon, waypoint.coordinate.longitude)
}
let loc = CLLocationCoordinate2DMake((maxLat-fabs(maxLat - minLat)/2), (maxLon-fabs(maxLon - minLon)/2))
let span = MKCoordinateSpanMake(0.001 + (1.0 + Setting.shared.regionPaddingFactor) * fabs(maxLat - minLat), 0.001 + (1.0 + Setting.shared.regionPaddingFactor) * fabs(maxLon-minLon))
// The 0.001 values above ensure that you do not get a 0.0 valued span if all of the points have the same latitude, longitude, or both, or if there is only one point
// The regionPaddingFactor is a constant to allow some space around the points passed in
let reg = MKCoordinateRegionMake(loc, span)
mapView.setRegion(reg, animated: true)
mapView.animatedZoom(zoomRegion: reg, duration: 0.8)
}
}
In the calling code, I provide for 3 settings that rotate as the user presses the button:
Show a tight view (just pass current location in the call)
Show the whole route (pass all points in the template)
Change to manual zooming / positioning
The last option is needed since I call the function whenever a new current position is received to reposition the view based on current location, which repeatedly refocuses the view if the user is trying to reposition the map.
If you don't want the flexibility of sending different position arrays, you can do the positioning here using mapView.annotations or just the current location as the array.

Update location only once

I am adding a mapKit for my app, and I setting the middle of the map to be the persons current location, in lat and lng. I am able to get it working, however, it keeps updating every so often. I only want it to update once when the app is loaded, then not update anymore. Maybe update every 2 minutes. Here is my code:
func locationManager(manager: CLLocationManager!,
didUpdateLocations locations: [AnyObject]!)
{
var latestLocation = locations.last as! CLLocation
let location = CLLocationCoordinate2D(latitude: latestLocation.coordinate.latitude, longitude: latestLocation.coordinate.longitude)
let span = MKCoordinateSpanMake(0.015, 0.015)
//Let our point be the center of our span
//region holds value of span and location
let region = MKCoordinateRegion(center: location, span: span)
//set up region on mapView
mapView.setRegion(region, animated: true)
//MKPointAnnotation defines a concrete annotation
let annotation = MKPointAnnotation()
}
If you are targeting only iOS 9, look at the new requestLocation() method. It addresses your problem. If you need to target older versions of iOS, you could add stopMonitoringLocation() to the
func locationManager(
manager: CLLocationManager!,
didUpdateLocations locations: [AnyObject]!
)
This will stop the monitoring once you get one reading...
I'm not sure what your starting you location manager with, but I think you might want to look at using:
startMonitoringSignificantLocationChanges()
to start your location manager. Here is apple's documentation on the subject: https://developer.apple.com/library/prerelease/ios/documentation/CoreLocation/Reference/CLLocationManager_Class/index.html#//apple_ref/occ/instm/CLLocationManager/startMonitoringSignificantLocationChanges

Resources