1

I am trying to detect if the user is near or enters my desired location. I am using CLLocationManager to monitor this action, but for some reason it doesn't work. I am in the current location but didEnterRegion not called. Here is my code:

class ViewController: UIViewController, CLLocationManagerDelegate {

    var locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()


        let center = CLLocationCoordinate2D(latitude: lat, longitude: long)
               if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
                   // Register the region.
                   let maxDistance = locationManager.maximumRegionMonitoringDistance
                   let region = CLCircularRegion(center: center,
                        radius: maxDistance, identifier: "identifier")
                   region.notifyOnEntry = true
                   region.notifyOnExit = false

                   locationManager.startMonitoring(for: region)

            }
    }


  func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
    if region is CLCircularRegion {
            print("entered....")
        }
    }

I also added privacy descriptions in plist file: enter image description here

How can I fix this issue?

Edited:

requestState(for region:) and didDetermineState:for: work now, but how can I check if the user is near or at the location? for example, if the user is near or in the region do something and if not do something else.

Edited 2

let center = CLLocationCoordinate2D(latitude: lat, longitude: long)
            let region = CLCircularRegion(center: center, radius: 1000, identifier: "Store")
            let controller = LocationController()

               controller.requestWhenInUseAuthorization()

               controller.requestState(for: region) { (state) in

                   switch state {
               case .inside:
                   print("inside region")
               case .outside:
                   print("outside region")
               case .unknown:
                   print("unknown")

                   }

               }
iOS.Lover
  • 5,923
  • 21
  • 90
  • 162
  • 1
    Are you already inside the region when you start monitoring? – Paulw11 Feb 18 '20 at 09:53
  • @Paulw11 yes..... – iOS.Lover Feb 18 '20 at 09:54
  • 3
    Then you should call [`requestState(for region:)`](https://developer.apple.com/documentation/corelocation/cllocationmanager/1423804-requeststate) and implement [`didDetermineState:for:`](https://developer.apple.com/documentation/corelocation/cllocationmanagerdelegate/1423570-locationmanager) – Paulw11 Feb 18 '20 at 09:56
  • `Boundary crossing notifications are delivered to your location manager's delegate object. Specifically, the location manager calls the locationManager(_:didEnterRegion:) or locationManager(_:didExitRegion:) methods of its delegate.` As Paulw11 says, these delegate methods are specifically for boundary crossings – Swinny89 Feb 18 '20 at 11:58
  • Thank you guys, `requestState(for region:)` and `didDetermineState:for:` work now, but how can I check if the user is near or at the location? for example, if the user is near or in the region do something and if not do something else. – iOS.Lover Feb 18 '20 at 14:59
  • make sure to register 20 location max, this is restriction form apple https://developer.apple.com/documentation/corelocation/monitoring_the_user_s_proximity_to_geographic_regions – a.masri Feb 22 '20 at 20:16
  • 1
    I probably don't understand your problem, but after you received a notification that you entered the region, can't you simply compute the distance of the current location to the specified location as `let distance = locationManager.location.distance(from center)`? – Reinhard Männer Feb 25 '20 at 08:27
  • Try this: https://stackoverflow.com/questions/19246493/locationmanagerdidenterregion-not-called-when-a-beacon-is-detected – Santosh Feb 25 '20 at 15:05

3 Answers3

1

For a one-off check, requestState(for:) is what you want.

CoreLocation will then determine the state and then call the locationManager(_:didDetermineState:for:) function on your delegate, giving you a CLRegionState enum which you can test for == .inside

Example:

import CoreLocation

class LocationController: CLLocationManagerDelegate {
    typealias RegionMonitoringClosure = (CLRegion, Bool) -> Void
    typealias RegionStateClosure = (CLRegion, CLRegionState) -> Void

    private let manager = CLLocationManager()
    private let stateClosure: RegionStateClosure
    private let monitoringClosure: RegionMonitoringClosure

    init(stateClosure: RegionStateClosure, monitoringClosure: RegionMonitoringClosure) {
        manager.delegate = self
        self.stateClosure = stateClosure
        self.monitoringClosure = monitoringClosure
    }

    func addMonitoredRegion(_ region: CLRegion) {
        manager.startMonitoring(for: region)
    }

    func remove(monitoredRegion region: CLRegion) {
        manager.stopMonitoring(for: region)
    }

    func requestState(for region: CLRegion) {
        manager.requestState(for: region)
    }

    // MARK: CLLocationManagerDelegate

    func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
        print("Entered \(region)")

        self.monitoringClosure(region, true)
    }

    func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
        print("Exited \(region)")

        self.monitoringClosure(region, false)
    }

    func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
        print("Started monitoring region \(region)")
    }

    func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
        print("Determined state \(state) for region \(region)")

        self.stateClosure(region, state)
    }
}

Usage:

let regionToTest: CLRegion = ... // your desired region.
let controller = LocationController(stateClosure: { region, state in 
    switch state {
    case .inside:
        print("inside region \(region)")
    case .outside:
        print("outside region \(region)")
    }
}, monitoringClosure: { region, entered in 
    if entered == true {
       print("Entered \(region)")
       //...
    }
})

controller.requestState(for: regionToTest)
Jacob Relkin
  • 161,348
  • 33
  • 346
  • 320
1

If you are running this on a simulator you can see different results than if you are running it on a device For example if you are already inside the Region you defined and you use the simulator to test. You may not always get the desired results. For some reason the simulator does not like it if you are inside the region you want to detect when you turn it on.

Furthermore, after many of my own tests using beacons, it turns out that distance from the region is NOT accurate when on a simulator. It has often been wrong when using the simulator. It is also HIGHLY affected by any physical interferences between your computer and your beacon. Lots of other devices in the area and metal can highly affect your simulator from reporting proper data. I recommend testing all your code on a physical device. When you load the device with the application. Unplug the device from your computer and turn on the app OUTSIDE of the beacon region.

For further and better testing you can create your own local "debugger" on the application so when you are using the device you can see the desired information without being connected to your computer.

Julian Silvestri
  • 1,970
  • 1
  • 15
  • 33
1

change

  locationManager.requestWhenInUseAuthorization()

to

  locationManager.requestAlwaysAuthorization()

also check your gpx file

Masoud Roosta
  • 455
  • 9
  • 19