I have been doing some iOS development for a couple of months and recently I am developing a bus app.
I am currently mimicking the bus' movements and set up multiple annotations on the bus stops. For test purposes, I have setup just one bus stop and am trying to monitor when the bus has entered this region and exited as well.
Strangely, my didStartMonitoringForRegion method is called perfectly but neither the didEnterRegion nor didExitRegion methods are called. Every time I run the program, the bus pretty much passes the stop without prompting me so.
Could someone explain to me why this is happening and how to resolve it?
let locationManager = CLLocationManager()
var allBusAnnotations = [MKPointAnnotation]()
var summitEastBusStations = [CLLocationCoordinate2D]()
var busStopNames = ["Dix Stadium", "Risman Plaza", "Terrace Drive", "Terrace Drive 2","C-Midway","Theatre Dr.","East Main Street","South Lincoln"]
var radius = 500 as CLLocationDistance
// 0.02 is the best zoom in factor
var mapZoomInFactor : Double = 0.02
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.getBusStop()
self.locationManager.delegate = self
// gets the exact location of the user
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
// gets the user's location only when the app is in use and not background
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
self.mapView.showsUserLocation = true
self.setBusStopAnnotations(summitEastBusStations)
// self.mapView.mapType = MKMapType.Satellite
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
// sends the latitude and longitude to the Apple Servers then returns the address
CLGeocoder().reverseGeocodeLocation(manager.location, completionHandler: { (placeMarks: [AnyObject]!, error: NSError!) -> Void in
if error != nil
{
println("Reverse Geocode Failed: " + error.localizedDescription)
return
}
if placeMarks.count > 0
{
// gets the most updated location
let pm = placeMarks.last as! CLPlacemark
let centre = CLLocationCoordinate2D(latitude: manager.location.coordinate.latitude, longitude: manager.location.coordinate.longitude)
// draws a circle in which the map will zoom to
let region = MKCoordinateRegion(center: centre, span: MKCoordinateSpan(latitudeDelta: self.mapZoomInFactor, longitudeDelta: self.mapZoomInFactor))
self.mapView.setRegion(region, animated: true)
self.displayLocationInfo(pm)
// self.distanceToClosestAnnotation(pm)
self.geoFencing()
// YOU CAN IGNORE THIS WHOLE PART. IT'S IRRELEVANT FOR THIS QUESTION
var repeatTimes = 0
var count = 0
while(count <= 7)
{
if count == (self.summitEastBusStations.count - 1)
{
count = 1
++repeatTimes
}
else if repeatTimes == 1
{
count = 0
++repeatTimes
}
else if repeatTimes == 2
{
break
}
self.distanceToBusStop(pm, count: count)
++count
}
}
})
}
func locationManager(manager: CLLocationManager!, didFailWithError error: NSError!) {
println("Location Manager Failed: " + error.localizedDescription)
}
func locationManager(manager: CLLocationManager!, didStartMonitoringForRegion region: CLRegion!) {
println("The region is monitored")
println("The monitored region is \(region.description)")
}
func locationManager(manager: CLLocationManager!, monitoringDidFailForRegion region: CLRegion!, withError error: NSError!) {
println("Failed to monitor the stated region")
}
func locationManager(manager: CLLocationManager!, didEnterRegion region: CLRegion!) {
println("The bus has entered the region")
}
func locationManager(manager: CLLocationManager!, didExitRegion region: CLRegion!) {
println("The bus has left the region")
}
func geoFencing()
{
let rismanPlaza = CLLocationCoordinate2D(latitude: 41.1469492, longitude: -81.344068)
var currentBusStop = CLLocation(latitude: rismanPlaza.latitude, longitude: rismanPlaza.longitude)
addRadiusCircle(currentBusStop)
let busStopRegion = CLCircularRegion(center: CLLocationCoordinate2D(latitude: rismanPlaza.latitude, longitude: rismanPlaza.longitude), radius: radius, identifier: busStopNames[1])
if radius > self.locationManager.maximumRegionMonitoringDistance
{
radius = self.locationManager.maximumRegionMonitoringDistance
}
locationManager.startMonitoringForRegion(busStopRegion)
}
// creates the radius around the specified location
func addRadiusCircle(location: CLLocation)
{
self.mapView.delegate = self
var circle = MKCircle(centerCoordinate: location.coordinate, radius: radius)
self.mapView.addOverlay(circle)
}
// performs the actual circle colouring
func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer!
{
if overlay is MKCircle
{
var circle = MKCircleRenderer(overlay: overlay)
circle.strokeColor = UIColor.redColor()
circle.fillColor = UIColor(red: 255, green: 0, blue: 0, alpha: 0.1)
circle.lineWidth = 1
return circle
}
else
{
return nil
}
}
I ended up using the CLRegion.containsCoordinate(location.coordinate) method instead. It works pretty much the same way.
Once the object has entered my set region, it returns true and from here I can know when it's entered and exited the region.
Please ensure [CLLocationManager regionMonitoringAvailable] returns YES and
CLLocationManager.monitoredRegions contains valid regions.
Also, from Apple documentation:
In iOS 6, regions with a radius between 1 and 400 meters work better
on iPhone 4S or later devices. (In iOS 5, regions with a radius
between 1 and 150 meters work better on iPhone 4S and later devices.)
On these devices, an app can expect to receive the appropriate region
entered or region exited notification within 3 to 5 minutes on
average, if not sooner.
And
Apps can expect a notification as soon as the device moves 500 meters
or more from its previous notification. It should not expect
notifications more frequently than once every five minutes. If the
device is able to retrieve data from the network, the location manager
is much more likely to deliver notifications in a timely manner.
There are many causes why your delegates are not triggering. First goto target settings and in capabilities tab check whether in BackgroundModes Location Updates is enabled.
If it is on then try to check whether your current location manager holds the region you've specified by checking
NSLog(#"Monitored Regions %#",self.locationManager.monitoredRegions);
Then if the user device is at current location(Latitude and longitude) the didEnterRegion: and didExitRegion: delegates will not be triggering. Use didDetermineState: method to find whether the user/device is in current region that is moniotred. If it so, didDetermineState: will be triggered. Once the user leaves the region didExitRegion: will be triggered.
Then after also if delegates aren't trigerring then find then error using the following in delegate
- (void)locationManager:(CLLocationManager *)manager monitoringDidFailForRegion:(CLRegion *)region withError:(NSError *)error {
NSLog(#"Failed to Monitor %#", error);
}
Related
I am building a feature related to region monitoring while starting region monitoring I am requesting the state as shown below in code. On some of the devices, I am getting region state Unknown all the time. If I switch Wifi On or Off or plug the charger into it. It starts working fine.
How can I make it more reliable on a cellular network?
Please, note I took all location permissions from the user before making any region monitoring or state request calls.
private func initiateLocationManager() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.distanceFilter = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
}
func startMonitoring(alarm: StationAlarm) {
if LocationManager.sharedInstance.isRegionMonitoringAvailable() {
let coordinate = CLLocationCoordinate2D(latitude: stationLatitude, longitude: stationLongitude)
// 1
let region = CLCircularRegion(center: coordinate, radius: CLLocationDistance(radius * 1000), identifier: alarm.alarmId)
// 2
region.notifyOnEntry = true
region.notifyOnExit = false
// 4
locationManager.startMonitoring(for: region)
Utility.delay(0.1) { [weak self] in
self?.locationManager.requestState(for: region)
}
}
}
func locationManager(_: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
Log.event("Region State is \(state.rawValue)")
}
The issue is, you are calling the requestState using a hard-coded delay - (0.1). How do you make sure the Location Manager started monitoring your region within 0.1 seconds? You will get the exact region state of a region, only if started monitoring it.
The better method for overcoming this problem is, implement the didStartMonitoringForRegion delegate and call requestStateForRegion
locationManager.startMonitoring(for: region)
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
manager.requestState(for: region)
}
func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
if (region is CLBeaconRegion) && state == .inside {
locationManager(manager, didEnterRegion: region)
}
}
From the CLLocationManager requestState(for:) docs:
region: The region whose state you want to know. This object must be an instance of one of the standard region subclasses provided by Map Kit. You cannot use this method to determine the state of custom regions you define yourself.
You defined the region yourself so you can't use requestState(for:) to get its state. You use that function with regions that you get back from Core Location (via the delegate methods).
If you want to know whether the device is currently inside a region, start a standard location update request (startUpdatingLocation() etc) and when you get back a recent and accurate coordinate, use the CLCircularRegion contains() function to check the coordinate.
// In the locationManager(_:didUpdateLocations:) delegate method
if myCircularRegion.contains(myCoordinate) {
// ...
}
I am trying to get geofencing working in CoreLocation but even though I am standing right on top of the lat/long point, didEnterRegion never gets called.
Here's my code:
override func viewDidAppear(_ animated: Bool) {
myLocationManager = CLLocationManager()
myLocationManager.delegate = self
myLocationManager.desiredAccuracy = kCLLocationAccuracyBest
if CLLocationManager.authorizationStatus() == .notDetermined {
myLocationManager.requestAlwaysAuthorization()
} else if CLLocationManager.authorizationStatus() == .authorizedAlways {
let geoSet = arrLocSets[2]
let regionLocation: CLLocation = CLLocation(latitude: geoSet[0], longitude: geoSet[1])
let region = CLCircularRegion(center: regionLocation.coordinate, radius: 30, identifier: "region1")
region.notifyOnEntry = true
region.notifyOnExit = false
myLocationManager.startMonitoring(for: region)
print(myLocationManager.monitoredRegions)
}
if CLLocationManager.locationServicesEnabled() {
myLocationManager.startUpdatingLocation()
}
}
the print call logs this:
CLCircularRegion (identifier:'region1', center:<+40.20675900,-75.48613300>, radius:30.00m)]
which is correct.
the print call in
func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
print("started monitoring!")
}
prints to the log so I know at least the monitoring started.
func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
print(region)
/*
if let region = region as? CLCircularRegion {
let myAlert = UIAlertController(title: "Entered Region", message: "You found the region \(region.identifier)", preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
myAlert.addAction(okAction)
present(myAlert, animated: true, completion: nil)
}
*/
}
and here's a screen capture from my phone, where I was tracking lat/long. (Ignore the bottom labels, those are previous hard coded coordinates).
I put a break point in didEnterRegion and it never get triggered. 206851 and 206759 should be within a 30M diameter of each other? No? Did I miss a set up step?
didEnterRegion/didExitRegion happens on status change only. When starting to monitor for a region while being inside of the region you will not receive didEnterRegion.
Start walking out until didExitRegion happens, and this will "rearm" the region for didEnterRegion events, now you can walk back in.
Just do not be surprised that you will have to walk much more than 30m in each direction to trigger the events. Be prepared for up to 300 meters journey - really depends on your speed and (wireless) environment. Region monitoring does not involve GPS at all, it is all about wifi/cell tower triangulation - depends on coverage, quality of prior measurements and it is not "real time", as it is driven by wifi/cell scans.
I'm trying to draw polyline in realtime to show users the route they have taken so far. I use google map api, and so far, it shows users' current location with no problem. But polyline doesn't work though (It doesn't draw polyline at all). I call startMonitoringSignificantLocationChanges after I check for authorization and draw the polyline inside didupdatelocations. Here's an relevant part of my code :
extension MapViewController: CLLocationManagerDelegate {
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
if status == .AuthorizedAlways {
locationManager.startUpdatingLocation()
mapView.myLocationEnabled = true
mapView.settings.myLocationButton = true
locationManager.startMonitoringSignificantLocationChanges()
}
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = manager.location {
mapView.camera = GMSCameraPosition(target: location.coordinate, zoom: 15, bearing: 0, viewingAngle: 0)
path.addCoordinate(CLLocationCoordinate2D(latitude: location.coordinate.latitude,
longitude: location.coordinate.longitude))
let polyline = GMSPolyline(path: path)
polyline.strokeColor = UIColor.redColor()
polyline.strokeWidth = 3
polyline.map = mapView
locationManager.stopUpdatingLocation()
}
}
UPDATE-1
After commenting out stopUpdatingLocation line, it does draw the line. But the line is mangled mess.
UPDATE-2
I figured out why the line is a mess instead of one straight line. It's because iphone is changing current location consistently(even though it is stationary), therefore, drawing multiple lines in a small area. How do I stop iphone from doing that?
UPDATE-3
I just found out that "Jumpy" current location does not only happen in my app. It also happens in GoogleMap app. So this is probably Iphone/ios GPS issue.
You just need to ignore invalid location updates or the updates with low accuracy.
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *location = [locations lastObject];
NSTimeInterval age = -[location.timestamp timeIntervalSinceNow];
if (age > 120) return; // ignore old (cached) updates
if (location.horizontalAccuracy < 0) return; // ignore invalid updates
if (location.horizontalAccuracy <=10) //you can change 10 to 20 if you want more frequent updates
{
// this is a valid update
}
}
Hope this helps.
I am trying to make a app where everyone can see everyones location at the same map. I can't find any tutorials on how to retrieve ALL users location on the same map.
I have manage to make a script which uploads the users location into Parse with this script:
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint?, Error : NSError?) -> Void in
if let geoPoint = geoPoint{
PFUser.currentUser()? ["location"] = geoPoint
PFUser.currentUser()?.saveInBackground()
I have also manage to get the location of the current user.
Any one know how i can display everyones location, not just mine?
Thank you for your time and help. I am very new to Swift so let me know if i need to provide more information.
Here is my display code.
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint?, Error : NSError?) -> Void in
if let geoPoint = geoPoint{
PFUser.currentUser()? ["location"] = geoPoint
PFUser.currentUser()?.saveInBackground()
self.MapView?.showsUserLocation = true
self.MapView?.delegate = self
MapViewLocationManager.delegate = self
MapViewLocationManager.startUpdatingLocation()
self.MapView?.setUserTrackingMode(MKUserTrackingMode.Follow, animated: false)
self.locationManager.requestAlwaysAuthorization()
self.locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled(){
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.locationManager.startUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocationManager]){
var locValue:CLLocationCoordinate2D = (manager.location?.coordinate)!
print("locations = \(locValue.latitude) \(locValue.longitude)")
Outside ViewWDidLoad
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
let region = MKCoordinateRegion(center: center, span: MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.MapView?.setRegion(region, animated: true)
self.locationManager.stopUpdatingLocation()
}
To get all users location is not so simple as you think. The most time regular app is working in the background. In the background app can be in background or suspended or terminated state ( about states). And you have to get location from any of these states. How is it possible: you should enable Background Mode for location tracking for your app. To do it simpler - use significant location tracking for all users, but accuracy will be about 500m. To get more accuracy (but more difficult implementation) you can send silent push notification to users (about silent push), it wakes up app from terminated or suspended states to background state, and now you can get and send users current location to your server.
My code is included below. It's a class intended to return the current location coordinates. The only output I get is "before after before after" - the print lines surrounding requestAlwaysAuthorization. And then the App crashes. The request dialog sometimes shows briefly, sometimes for a few seconds. On few occasions I even get to press "OK". The App always crashes. I've had this problem in xCode 7.0 and now xCode 7.0.1, iOS 9.0 in both cases. I have searched StackOverflow high and low, and most questions on this topic are for earlier versions of both xCode and iOS, and none of the posted solutions helped in my case. Hence, this question. I've also found YouTube tutorials that do basically what I'm doing, but no joy. I also have NSLocationAlwaysUsageDescription in my plist, and I have Privacy - Location Usage Description and NSLocationWhenInUseUsageDescription for good measure. I have also tried sending a location from xCode via the Product\Scheme menu, and I have also tried using the simulator's Debug\Location. I've tried a few different location options for each. The simulator's Map app always seems to work. And Apple's LocateMe (written in Objective C) also works. Swift 2 (my code below) fails.
import CoreLocation
class TheCurrentLocation: NSObject, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
var latitude: Double = 0
var longitude: Double = 0
var locationStatus: NSString = "Not Started"
func initialize() {
self.locationManager = CLLocationManager()
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
print("before")
self.locationManager.requestAlwaysAuthorization()
// self.locationManager.requestWhenInUseAuthorization()
print("after")
} // END: initialize()
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
self.locationManager.stopUpdatingLocation()
print("ERRORS: " + error.localizedDescription )
} // END: locationManager delegate didFailWithError
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations.last
let center = CLLocationCoordinate2D(latitude: location!.coordinate.latitude, longitude: location!.coordinate.longitude)
self.locationManager.stopUpdatingLocation()
print ("ta da")
self.latitude = center.latitude
self.longitude = center.longitude
} // END: locationManager delegate didUpdateLocations
func locationManager(manager: CLLocationManager, didChangeAuthorizationStatus status: CLAuthorizationStatus) {
var isAllowed = false
switch status {
case CLAuthorizationStatus.Restricted:
locationStatus = "Restricted Access to Location"
case CLAuthorizationStatus.Denied:
locationStatus = "User Denied Access to Location"
case CLAuthorizationStatus.NotDetermined:
locationStatus = "Location Status Not Determined"
default:
locationStatus = "Allowed Access to Location"
isAllowed = true
} // END switch status
if (isAllowed == true) {
NSLog(String(locationStatus))
self.locationManager.startUpdatingLocation()
} else {
NSLog(String(locationStatus))
}
} // END: locationManager delegate didChangeAuthorizationStatus
} // END: theCurrentLocation
I think I know what may be causing it. You cant have both NSLocationAlwaysUsageDescription and NSLocationWhenInUseUsageDescription. Only one of them. I think I ran into this problem before. Try to remove one of them and see if that fixes it.