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