0

I have 42 geolocation regions to monitor, I know that Apple only allows 20 at a time, so I tried to employ the answer that was given here: How to monitor more than 20 regions?

But I still can't trigger a notification at a region above 20. I've been trying to figure this out for days now and I feel like I'm just not seeing something. Can someone help please? The CLLocationManagerDelegate block of code is below, but if you wanted to see the entire ViewController for this part I put it here: full ViewController

extension SearchFormViewController: CLLocationManagerDelegate {

func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    
    var currentLocation : CLLocation?{
        didSet{
            evaluateClosestRegions()
        }
    }

    let allRegions : [CLRegion] = [] // Fill all your regions
    
    func evaluateClosestRegions() {

        var allDistance : [Double] = []

        //Calulate distance of each region's center to currentLocation
        for region1 in allRegions{
            let circularRegion = region1 as! CLCircularRegion
            let distance = currentLocation!.distance(from: CLLocation(latitude: circularRegion.center.latitude, longitude: circularRegion.center.longitude))
            allDistance.append(distance)
        }
        
        guard let location = locations.last else {
            return
        }
        currentLocation = location
        // a Array of Tuples
        let distanceOfEachRegionToCurrentLocation = zip(allRegions, allDistance)

        //sort and get 20 closest
        let twentyNearbyRegions = distanceOfEachRegionToCurrentLocation
            .sorted{ tuple1, tuple2 in return tuple1.1 < tuple2.1 }
            .prefix(20)

        // Remove all regions you were tracking before
        for region1 in locationManager.monitoredRegions{
            locationManager.stopMonitoring(for: region1)
        }

        twentyNearbyRegions.forEach{
            locationManager.startMonitoring(for: $0.0)
        }

    }

}
AM DeVito
  • 1
  • 1
  • Your didUpdateLocations function appears to do nothing. You declare some local variables and functions, then you do nothing and return from the function. You don't even call the evaluateClosestRegions function that you define – Shadowrun Aug 26 '22 at 15:48
  • And you haven't put any regions in: allRegions : [CLRegion] = [] // Fill all your regions. <-- you haven't filled that array with your regions – Shadowrun Aug 26 '22 at 15:53
  • @shadownrun oops yes ok i will enter the regions there. Am I not calling the evaluateClosestRegions here? Or do I need to call it some where outside: var currentLocation : CLLocation?{ didSet{ evaluateClosestRegions() } } – AM DeVito Aug 26 '22 at 16:04
  • You call it from the setter of current location, but that isn't called. Even if it was, it calls evaluateClosestRegions and evaluateClosestRegions calls the setter currentLocation = location, and that calls evaluateClosestRegions and so on – Shadowrun Aug 26 '22 at 16:10
  • Can i call it anywhere within func locationManager? thank you so much for your help. – AM DeVito Aug 26 '22 at 16:16

1 Answers1

0

A number of things, first define your regions, move that let allRegions to be a property on the view controller.

I haven't tested this but I would change allRegions to be an array of CLCircularRegion since that's all we need anyway, that gets rid of the type casting:

SearchFormViewController {

        let allRegions : [CLCircularRegion] = [
        // TODO actually have your regions in here
            CLCircularRegion(center: CLLocationCoordinate2D(latitude: 1, longitude: 2), radius: 200, identifier: "A"),
            CLCircularRegion(center: CLLocationCoordinate2D(latitude: 2, longitude: 3), radius: 100, identifier: "B"),
        // etc
    ] 
    

Second move evaluateClosestRegions out into a method on the view controller, no need for it to be a nested function. I also have it take a location in as an argument:

        func evaluateClosestRegions(from location: CLLocation) {
            // sort and get 20 closest
            let twentyNearbyRegions: [(CLCircularRegion, CLLocationDistance)] = allRegions.map { region in
        let center = CLLocation(latitude: circularRegion.center.latitude, 
                                        longitude: circularRegion.center.longitude)
        let distance = center.distance(from: location)
            }
                .sorted { $0.1 < $1.1 }
                .prefix(20)

            // Remove all regions you were tracking before
            for region in locationManager.monitoredRegions {
                locationManager.stopMonitoring(for: region)
            }

            twentyNearbyRegions.forEach {
                locationManager.startMonitoring(for: $0.0)
            }

        }
}

Importantly, in the location manager delegate, call the evaluateClosestRegions function, if you have a location. You may also want to consider only calling that if the user has moved enough since the last time you checked

extension SearchFormViewController: CLLocationManagerDelegate {
      func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let currentLocation = locations.last {
        evaluateClosestRegions(from: currentLocation)
        }
     }

I would also suggest one idea to improve your code which is basically to make your data smarter so that your code doesn't have to be so smart. If you introduce a struct that represents your data:

struct Content: Identifiable, Equatable, Hashable {
    static func == (lhs: SearchFormViewController.Content, rhs: SearchFormViewController.Content) -> Bool {
        lhs.id == rhs.id
    }

    var id: Int
    var title: String
    var center: CLLocationCoordinate2D
    var radius: CLLocationDistance = 150

    var region: CLCircularRegion {
        let region = CLCircularRegion(center: center, radius: radius, identifier: "Geofence\(id)")
        region.notifyOnEntry = true
        region.notifyOnExit = true
        return region
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
}

Now you can define any number of content items:

var allContent: [Content] = [
    Content(id: 1, title: "The Lime Light", center: .init(latitude: 45.49894, longitude: -73.5751419)),
    Content(id: 2, title: "Sans Soleil Bar", center: .init(latitude: 45.5065647, longitude: -73.5626957)),
    Content(id: 3, title: "S.A.T.", center: .init(latitude: 45.5098557, longitude: -73.5658257))
]

And put them into a collection when they are found etc:

var found: Set<Content> = []
var library: Set<Content> = []

This becomes simple:

func resetContentOnSignOut() {
    found = []
    library = []
}

func didFind(contentId: Int) {
    if let content = allContent.first(where: { $0.id == contentId }) {
        found.insert(content)
        library.insert(content)
    }
}

func hasFound(_ contentId: Int) -> Bool {
    found.contains { $0.id == contentId }
}

func content(withRegionIdentifier id: String) -> Content? {
    found.first { $0.region.identifier == id }
}

func locationManager(_ manager: CLLocationManager, didEnterRegion region1: CLRegion) {
    print("User has entered \(region1.identifier)")///

    if let content = content(withRegionIdentifier: region1.identifier),
       !hasFound(content.id) {
        didFind(content)
    }
}

And you can remove a lot of duplicate code like there are lots of places that do the same things:

func didFind(_ content: Content) {
    found.insert(content)
    library.insert(content)

    contentFoundButtonActive()
    sendNotification()
    storeUserGeofences()
    addToAudioGemCounter()
    updateGemsCollectedCounter()
    print("Content \(content.id) Found: \(Array(self.found)).")
}

Just as a general idea, this isn't meant to be working code

Shadowrun
  • 3,572
  • 1
  • 15
  • 13
  • Another way to do this is to have "container regions" that contain groups of 19 regions. Initially only monitor container regions.When you enter a container region, remove all other regions and start monitoring that container region and it's child regions. When you leave a container region, go back to monitoring just container regions. – Duncan C Aug 26 '22 at 16:24