Casting CLLocationDistance in Swift - ios

I'm a complete newbie using Swift and I'm having a headache trying to do something that should be quite straightforward.
I have an object Place with a variable of type Double called distance. I'm using the following code to store the distance between the user location and some other locations:
var obj = res as Place
let loc = CLLocation(latitude: obj.location.lat, longitude: obj.location.lng)
let dist = CLLocation.distanceFromLocation(loc)
obj.distance = dist // ERROR
The last line shows me an error saying "CLLocationDistance is not convertible to 'Double'". As far I know, CLLocationDistance should be a Double. I've tried to cast that value to double and float using
Double(dist)
and
obj.distance = dist as Double
but nothing seems to work. I would appreciate any help.

The reason this is occurring boils down to the following line:
let distance = CLLocation.distanceFromLocation(loc)
This is not doing what you think it's doing. distanceFromLocation is an instance method, but you are calling it as a class method. Unlike ObjC, Swift allows this, but what you're actually getting back is not a CLLocationDistance but a function with the following type signature: CLLocation -> CLLocationDistance. You can verify this by adding in the type annotation yourself:
let distance: CLLocation -> CLLocationDistance = CLLocation.distanceFromLocation(loc)
This is called partial application and is outside the scope of my answer, except to note that the first parameter of the function assigned to distance would be the implicit self when called as an instance method.
The fix is simple:
let distance = loc.distanceFromLocation(loc)
Then everything will work as you desire, at least from the perspective of the type system, because this properly returns a CLLocationDistance, which is substitutable for Double.

You need two objects of CLLocation with two different locations
Like as fromLocation and toLocation
Then you will get distance in
let distance = fromLocation.distanceFromLocation(toLocation)
An Example of Code for distance between old and new location :
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
if let firstLocation = locations.first as? CLLocation
{
mapView.setCenterCoordinate(firstLocation.coordinate, animated: true)
let region = MKCoordinateRegionMakeWithDistance(firstLocation.coordinate, 1000, 1000)
mapView.setRegion(region, animated: true)
if let oldLocation = oldLocation {
let delta: CLLocationDistance = firstLocation.distanceFromLocation(oldLocation)
totalDistane += delta
updateDistanceLabel()
}
oldLocation = firstLocation
}
}

Related

Can a CLLocationManager manage 2 or more locations at the same time in swift 3

i have a problem that i cant seem to find the answer anywhere, i have a global CLLocationManager() variable, in the viewController, so that i can see when the user enters or leave a location with the locationManager callBacks, and it works perfectly with only one location at a time, but i want to make the app monitor 2 or more locations at the same time.
I created a function to start it:
var coreLocationManger = CLLocationManager()
func setMonitoredRegion(location:CLLocation) {
let startLocation = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
let monitoredRegion = CLCircularRegion(center: startLocation, radius: 100, identifier: "Local Region")
coreLocationManger.startMonitoring(for: monitoredRegion)
coreLocationManger.allowsBackgroundLocationUpdates = true
coreLocationManger.delegate = self
monitoredRegion.notifyOnEntry = true
monitoredRegion.notifyOnExit = true
}
this func, asks for a location to be managed, and when i use this multiple times with various locations, it just manages the last location that was given!
Do you guys have any tips on that?
Thank you
You keep overwriting the region each time you call the function. Make sure region identifier is different for each location.

Calculating total distance traveled not accurate

Using the below code the distance traveled is always lower than the actual distance traveled by 20-40% when traveling at least a couple of miles. This is traveling in a large city with strong cell and GPS signal the entire trip so I know that is not the issue. I verified the actual distance using Google Maps and MapMyHike. Any Ideas?
Relevant parts of the code pasted below:
let metersToMiles: Double = 0.000621371
var startLocation: CLLocation!
var lastLocation: CLLocation!
var distanceTraveled: Double = 0
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.requestAlwaysAuthorization() // Location permission for background
self.locationManager.requestWhenInUseAuthorization() // Location permission for foreground
if CLLocationManager.locationServicesEnabled() {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
locationManager.distanceFilter = kCLDistanceFilterNone
locationManager.activityType = CLActivityType.Fitness
locationManager.pausesLocationUpdatesAutomatically = false
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if startLocation == nil {
print("startLocation is null")
startLocation = locations.first
} else {
let lastLocation = locations.last
let distance = startLocation.distanceFromLocation(lastLocation!)
startLocation = lastLocation
distanceTraveled += distance
}
updateDistanceTraveledText()
}
func updateDistanceTraveledText() {
let distanceTraveledString = "\(String(format:"%.1f", distanceTraveled * metersToMiles))"
distanceTraveledText.text = distanceTraveledString
}
I also tried all types of desiredAccuracy like kCLLocationAccuracyBest and kCLLocationAccuracyNearestTenMeters, etc. and not setting activityType at all, and also setting it to AutomotiveNavigation/Fitness all to no avail.
It’s possible you’re getting a string of location updates as it zeros in on your precise location. Those updates indicate a change in precision, not actual motion — but it looks like your code is going to register them as motion anyway. This can happen no matter how high you set desiredAccuracy because it’s a maximum accuracy, not a minimum.
You could debug this by logging the horizontalAccuracy and verticalAccuracy properties of the CLLocations as they come in.
If the problem is indeed that you’re getting reports with varying accuracy, some possible solutions are:
Use the distace filter on CLLocationManager to ignore small travel.
Discard any CLLocation that isn’t accurate enough, according to some threshold you set.
If the latest location falls within the accuracy circle of the previous one (and thus may not represent actual motion), replace the last location instead of adding the new one.
Use some sort of sophisticated Bayesian inference to find the path of maximum likelihood through all reported points.
I’d do #1 or #2 if this is casual, #3 if you really care, and #4 only if maximum accuracy is essential … and you know more about the math than I do.

CLLocation Manager how to update after certain distance

I am using CLLocationManager didupdatelocations like so:
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
location = locations.last as? CLLocation
NSNotificationCenter.defaultCenter().postNotificationName("location", object: self)
}
This is all working fine but I only want to post the notification if the location is a certain distance away from the original location. Can I just use
locations.first
and compare that to locations.last seems like that would only update for the original not if the user continues moving around a city.
To calculate the distance you will need two CLLocation (let's say, newLocation and oldLocation). You can calculate the distance between this two locations using:
let distance = Double(newLocation.distanceFromLocation(oldLocation))
After that just add you logic to decide when to post the notification:
if distance > myMinimum distance{
NSNotificationCenter.defaultCenter().postNotificationName("location", object: self)
}
Note, this is the shortest distance calculated between to points (straight line) it does not calculate the route distance.
If you want to calculate the route distance between two points you need to use MKDirectionsRequest, this will return you one, or many, routes from point A to point B with the step by step instruction:
class func caculateDistance(){
var directionRequest = MKDirectionsRequest()
var sourceCoord = CLLocationCoordinate2D(latitude: -36.7346287, longitude: 174.6991812)
var destinationCoord = CLLocationCoordinate2D(latitude: -36.850587, longitude: 174.7391745)
var mkPlacemarkOrigen = MKPlacemark(coordinate: sourceCoord, addressDictionary: nil)
var mkPlacemarkDestination = MKPlacemark(coordinate: destinationCoord, addressDictionary: nil)
var source:MKMapItem = MKMapItem(placemark: mkPlacemarkOrigen)
var destination:MKMapItem = MKMapItem(placemark: mkPlacemarkDestination)
directionRequest.setSource(source)
directionRequest.setDestination(destination)
var directions = MKDirections(request: directionRequest)
directions.calculateDirectionsWithCompletionHandler {
(response, error) -> Void in
if error != nil { println("Error calculating direction - \(error.localizedDescription)") }
else {
for route in response.routes{
println("Distance = \(route.distance)")
for step in route.steps!{
println(step.instructions)
}
}
}
}
}
This example code will return you this:
Disntance
Distance = 16800.0
Step by Step instructions
Start on the route
At the end of the road, turn left onto Bush Road
Turn right onto Albany Expressway
At the roundabout, take the first exit onto Greville Road toward 1, Auckland
At the roundabout, take the third exit to merge onto 1 toward Auckland
Keep left
Take exit 423 onto Shelly Beach Road
Continue onto Shelly Beach Road
At the end of the road, turn right onto Jervois Road
Turn left onto Islington Street
Keep right on Islington Street
Arrive at the destination
The function can be easily modified to receive two locations and return the distance and any other needed information.
I hope that helps you!

How to trigger an EventKit alarm with Coordinates in Swift

I don't know about anyone else, but EventKit seems to have very little in terms resources and tutorials online for you to refer to for help.
I need to trigger an alarm when a user hits a radius of a set of coordinates, I wasn't sure of the best ay to do this, I was torn between local notifications, and EventKit reminders.
I decided to go for eventKit as I felt that I could do more with more the alarms and it was the most practical way to do it however, having not known much about EventKit i had some issues.
Anyway I've managed to get together and build a sample project which works and triggers an alert when the user leaves their current location, the only problem is, is that I almost want to do the complete opposite of that, I want to trigger an alert when the user enters a set of coordinates, I assume that most of the code is transferrable, however I seem to be stuck on one bit mainly.
// Creates an EKStructuredLocation Instance with a title of "Current Location"
let location = EKStructuredLocation(title: "Current Location")
// Uses the last location update extracted from the locations array to supply you're current location
location.geoLocation = locations.last as! CLLocation
// Location is added to a newly created alarm instance
let alarm = EKAlarm()
alarm.structuredLocation = location
// This alarm is triggered when the user moves away from the location proximity
alarm.proximity = EKAlarmProximityLeave
stationReminder.addAlarm(alarm)
I'm struggling to find how to set the location of the alarm to coordinates rather than users location.
I tried changing this
location.geoLocation = locations.last as! CLLocation
to
location.geoLocation = CLCircularRegion(circularRegionWithCenter: CLLocationCoordinate2D(latitude: 37.33233141, longitude: -122.03121860), radius: 50.0, identifier: "Location1")
but this doesn't work, i believe i am on the right track but i am throwing up this error: Cannot assign a value of type 'CLCircularRegion!' to a value of type 'CLLocation!'
I've tried loads of things with no resolve, does anybody have any experience with this and know how to help?
I also assume i'll have to change the following from this
alarm.proximity = EKAlarmProximityLeave
to this
alarm.proximity = EKAlarmProximityEnter
UPDATE
I've taken on board some comments below and tried a bunch of other things to get this to work, I feel like I am so close but somethings just missing. I cannot get this alarm to trigger. Excuse all the code comments, it's just so you can see some of the attempts i have made at fixing this.
can anyone see anything wrong with this code for the alarm?
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// Stops location manager from sending further updates
locationManager.stopUpdatingLocation()
// Creates a new EKReminder which is named and initialised with text from the UITextField
let stationReminder = EKReminder(eventStore: appDelegate!.eventStore)
stationReminder.title = locationText.text
// Stores the previously created EKReminder in the default calendar
stationReminder.calendar = appDelegate!.eventStore!.defaultCalendarForNewReminders()
// Creates an EKStructuredLocation Instance with a title of "Current Location"
let location = EKStructuredLocation(title: "Destination: Bournemouth Station")
// Uses the last location update extracted from the locations array to supply you're current location
// location.geoLocation = locations.last as! CLLocation
// location.geoLocation = CLCircularRegion(circularRegionWithCenter: CLLocationCoordinate2D(latitude: 37.33233141, longitude: -122.03121860), radius: 50.0, identifier: "Location1")
// location.geoLocation = CLLocation(latitude: 50.742771, longitude: -1.895072)
location.radius = 50.0
location.geoLocation = CLLocation(latitude:50.742771, longitude:-1.895072)
// location.radius = 10.0 // metres
// Location is added to a newly created alarm instance
let alarm = EKAlarm()
alarm.structuredLocation = location
// This alarm is triggered when the user moves away from the location proximity
// alarm.proximity = EKAlarmProximityEnter
alarm.proximity = EKAlarmProximityEnter // "geofence": we alarm when *arriving*
// but this will have no effect until Reminders is granted Location access...
// and in iOS 8 it won't even ask for it until it is launched
// also, in iOS 8 the separate background usage pref is withdrawn;
// instead, auth of Reminders for "when in use" covers this...
// ...because it means "this app *or one of its features* is visible on screen"
stationReminder.addAlarm(alarm)
// Now we have a fully configured reminder which we save in the Event Store
var error: NSError?
appDelegate!.eventStore?.saveReminder(stationReminder,
commit: true, error: &error)
if error != nil {
println("Reminder failed with error \(error?.localizedDescription)")
}
}
Looks like EKEvent has an EKStructuredLocation, which you are using correctly. However, you need to be careful of the type of the geoLocation property. It should be a CLLocation, which is not the same as a CLCircularRegion.
Steps to fix:
check the docs for EKStructuredLocation https://developer.apple.com/library/mac/documentation/EventKit/Reference/EKStructuredLocationClassRef/index.html
set the location.geoLocation to a CLLocation that you create from latitude, longitude coordinates. (check the docs for CLLocation: https://developer.apple.com/library/mac/documentation/CoreLocation/Reference/CLLocation_Class/index.html#//apple_ref/swift/cl/CLLocation)
geoLocation = CLLocation(latitude: 37.33233141, longitude: -122.03121860)
set the geoLocation.radius separately location.radius = 50.0
at that point setting proximityEnter should work as you expected

iOS 7 Parse Geolocation

I am working on an app for iOS 7 and I need to find the location of the user and then check to see what other users are also at that same location. I need it to update as soon as the user opens the app as well as update every so often and will display the users at the same location. I have looked at the available examples, but there doesn't seem to be enough on this using Parse. Can anyone give me any help on how to go about doing this, or if anyone knows of some examples similar to what I'm trying to do I would greatly appreciate it. Thanks in advance!
You need to break your problem down and tackle the various pieces -
Obtain the user's location when the app opens and periodically
The Location and Maps programming guide is a good starting point. You can use CLLocationManager to obtain your initial location and you can register for the significant location change event to get updates periodically, even when your app isn't running. Apple has example code plus there are plenty of other examples out there - Just search using "Core Location examples"
Store the user's location in your Parse database
There are examples and documentation on Parse.com showing how to do this. You can also provide a web service that allows your app to query the database for other users at the same location
Identify other users at the same location when your app isn't running
You can use Parse background jobs to trawl your database, match user locations and send push notifications to your users. Again there are examples on Parse.com showing how to set up background jobs and push notifications
this may help this will get your current location then save to parse, its just a matter of querying parse to pull the data down
func locationManager(manager: CLLocationManager!, didUpdateLocations
locations: [AnyObject]!) {
var userLocation : CLLocation = locations[0] as! CLLocation
var latitude = userLocation.coordinate.latitude
var longitude = userLocation.coordinate.longitude
var latDelta : CLLocationDegrees = 0.01
var lonDelta : CLLocationDegrees = 0.01
var span : MKCoordinateSpan = MKCoordinateSpanMake(latDelta,
lonDelta)
var location : CLLocationCoordinate2D = CLLocationCoordinate2DMake(latitude, longitude)
var region : MKCoordinateRegion = MKCoordinateRegionMake(location, span)
self.mapView.setRegion(region, animated: true)
let testObject = PFObject(className: "User")
let currentPoints = PFGeoPoint(latitude: latitude, longitude: longitude)
testObject.setValue(currentPoints, forKey: "currentLocation")
testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
if (success) {
dispatch_async(dispatch_get_main_queue()) {
println("added to parse")
}
} else {
println(error)
}
}
}

Resources