title: Swift iOS Geofence from Background
body: <p>what is the best way to refresh geofence regions in background?</p>
  
  <p>At the moment i use location service with background mode always.
  I try to grab the <code>userlocation</code> and compare the distance to an initial saved location. When the user moved a distance greater my half <code>mapSpan</code> the app clears all <code>monitoredRegions</code>, makes a <code>firebase fetch</code> for search terms and performs a <code>MKlocalSearchRequest</code> with the search terms.</p>
  
  <p>All found <code>MKMapItems</code> form the <code>MKlocalSearchRequest</code> shoulb be set as new <code>GeofenceRegion</code>.</p>
  
  <p>It seems that the <code>background refresh</code> of the <code>monitoredRegions</code> are not working correctly. </p>
  
  <p>When I start the app all initial nearby <code>monitoredRegions</code> getting triggered. But when I drive a longer distance the <code>background refresh</code> for <code>new regions</code> seem not to update.</p>
  
  <p>Do I need to use a different background operation to get it work as I want?</p>
  
  <p>Here are some code details:
  In the <code>MKMapViewDelegate didUpdate userLocation</code> I look for my needs to update for new geofence regions.</p>
  
  <pre><code>func mapView(_ mapView: MKMapView, didUpdate userLocation: MKUserLocation) {
      lbl_DebugMonitoredRegions.text = "Current monitored regions: \(locationManager.monitoredRegions.count)"
      self.userLocation = userLocation.coordinate
      mapView.centerCoordinate = userLocation.coordinate
      let region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, CLLocationDistance(exactly: mapSpan)!, CLLocationDistance(exactly: mapSpan)!)
      mapView.setRegion(region, animated: false)
  
      if UserDefaults.standard.bool(forKey: eUserDefaultKey.isInitialLocationUpdate.rawValue){            
          //Update initial User Position
          UpdateLastUserLocationFromUserDefaults(coordinate: userLocation.coordinate)
  
          // Stop monitoring old regions
          self.StopMonitoringForOldRegions()
  
          //Remove old Geofence Overlays
          self.RemoveOldGeofenceOverlays()
  
          //Search nearby Shops
          PerformLocalShopSearch()
      }
      else if  UserDefaults.standard.bool(forKey: eUserDefaultKey.NeedToUpdateGeofence.rawValue){
          // Stop monitoring old regions
          self.StopMonitoringForOldRegions()
  
          //Remove old Geofence Overlays
          self.RemoveOldGeofenceOverlays()
  
          //Search nearby Shops
          PerformLocalShopSearch()
      }
      else if HasUserMovedDistanceGreaterMapSpan(userLocation: userLocation){
          // Stop monitoring old regions
          self.StopMonitoringForOldRegions()
  
          //Remove old Geofence Overlays
          self.RemoveOldGeofenceOverlays()
  
          //Search nearby Shops
          PerformLocalShopSearch()
      }
  }
  </code></pre>
  
  <p>Here I check for distance to saved initial position</p>
  
  <pre><code>//HasUserMovedDistanceGreaterMapSpan
  private func HasUserMovedDistanceGreaterMapSpan(userLocation:MKUserLocation) -&gt; Bool{
      if  let lastUserLocation = ReadLastUserLocationFromUserDefaults(){
          let distance = userLocation.location?.distance(from: lastUserLocation)
          if distance != nil &amp;&amp; distance! &gt; mapSpan * 0.25{
              lbl_DebugHasUserMovedDistance.text = "User moved &gt; mapSpan: \(debugcounter += 1)"
              UpdateLastUserLocationFromUserDefaults(coordinate: userLocation.coordinate)
              return true
          }
      }
      return false
  }
  private func ReadLastUserLocationFromUserDefaults() -&gt; CLLocation?{
      let latitude = UserDefaults.standard.double(forKey: eUserDefaultKey.LastUserLatitude.rawValue)
      let longitude = UserDefaults.standard.double(forKey: eUserDefaultKey.LastUserLongitude.rawValue)
      if latitude &gt; 0 &amp;&amp; longitude &gt; 0{
          return CLLocation(latitude: CLLocationDegrees(floatLiteral: latitude), longitude: CLLocationDegrees(floatLiteral: longitude))
      }
      else { return nil }
  }
  private func UpdateLastUserLocationFromUserDefaults(coordinate: CLLocationCoordinate2D) -&gt; Void{
      //Set hasUserChangedGeofenceRadius false
      UserDefaults.standard.set(false, forKey: eUserDefaultKey.NeedToUpdateGeofence.rawValue)
      //Set isInitialLocationUpdate false
      UserDefaults.standard.set(false, forKey: eUserDefaultKey.isInitialLocationUpdate.rawValue)
      //SaveNew position
      UserDefaults.standard.set(coordinate.latitude, forKey: eUserDefaultKey.LastUserLatitude.rawValue)
      UserDefaults.standard.set(coordinate.longitude, forKey: eUserDefaultKey.LastUserLongitude.rawValue)
  }
  </code></pre>
  
  <p>RemoveOldGeofenceOverlays and StopMonitoringForOldRegions</p>
  
  <pre><code> private func RemoveOldGeofenceOverlays() -&gt; Void{
      for overlay in self.MapView.overlays{
          if overlay is MKUserLocation{ }
          else { MapView.remove(overlay)}
      }
  }
  func StopMonitoringForOldRegions(){
      for region in locationManager.monitoredRegions {
          locationManager.stopMonitoring(for: region)
          NSLog("removing Region: " + region.identifier)
          NSLog("Monitored regions \(self.locationManager.monitoredRegions.count)")
      }
  }
  </code></pre>
  
  <p>My PerformLocalShopSearch</p>
  
  <pre><code> private func PerformLocalShopSearch() -&gt; Void{
      let rad = UserDefaults.standard.double(forKey: eUserDefaultKey.MonitoredRadius.rawValue)
      radiusToMonitore = CLLocationDistance(exactly: rad)
      if radiusToMonitore == 0 { return }
  
      firebaseShoppingList.GetStoresForGeofencing()
  }
  </code></pre>
  
  <p>My delegate when stores are received from Firebase</p>
  
  <pre><code> func StoresCollectionReceived() {
      for store in StoresArray {
          let request = MKLocalSearchRequest()
          request.naturalLanguageQuery = store
          request.region = MapView.region
          let search = MKLocalSearch(request: request)
          search.start { (response, error) in
              if error != nil {
                  print(error!.localizedDescription)
                  return
              }
              if response!.mapItems.count == 0 {
                  NSLog("No local search matches found for \(store)")
                  return
              }
              NSLog("Matches found for \(store)")
  
              self.StartMonitoringGeofenceRegions(mapItems: response!.mapItems)
          }
      }
  }
  </code></pre>
  
  <p>And at least my functions for start monitoring circular regions</p>
  
  <pre><code>    internal func StartMonitoringGeofenceRegions(mapItems: [MKMapItem]){
      if self.userLocation == nil { return }
      var possibleRegionsPerStore = Int(round(Double(StoresArray.count / 20)))
      possibleRegionsPerStore = possibleRegionsPerStore &lt; 4 ? 4: possibleRegionsPerStore
  
      var itemsCount = 0
      itemsCount = TryMonitoreRegion(mapItems: mapItems, possibleRegionsPerStore: possibleRegionsPerStore, itemsCount: itemsCount, minDistance: 0, maxDistance: mapSpan * 0.1)
  
      if itemsCount == possibleRegionsPerStore { return }
      itemsCount = TryMonitoreRegion(mapItems: mapItems, possibleRegionsPerStore: possibleRegionsPerStore, itemsCount: itemsCount, minDistance: mapSpan * 0.1, maxDistance: mapSpan * 0.2)
  
      if itemsCount == possibleRegionsPerStore { return }
      itemsCount = TryMonitoreRegion(mapItems: mapItems, possibleRegionsPerStore: possibleRegionsPerStore, itemsCount: itemsCount, minDistance: mapSpan * 0.2, maxDistance: mapSpan * 0.3)
  
      if itemsCount == possibleRegionsPerStore { return }
      itemsCount = TryMonitoreRegion(mapItems: mapItems, possibleRegionsPerStore: possibleRegionsPerStore, itemsCount: itemsCount, minDistance: mapSpan * 0.3, maxDistance: mapSpan * 0.4)
  
      if itemsCount == possibleRegionsPerStore { return }
      itemsCount = TryMonitoreRegion(mapItems: mapItems, possibleRegionsPerStore: possibleRegionsPerStore, itemsCount: itemsCount, minDistance: mapSpan * 0.4, maxDistance: mapSpan * 0.6)
  
      if itemsCount == possibleRegionsPerStore { return }
      itemsCount = TryMonitoreRegion(mapItems: mapItems, possibleRegionsPerStore: possibleRegionsPerStore, itemsCount: itemsCount, minDistance: mapSpan * 0.6, maxDistance: mapSpan * 3)
  }
  private func TryMonitoreRegion(mapItems:[MKMapItem], possibleRegionsPerStore:Int, itemsCount:Int, minDistance:Double, maxDistance:Double) -&gt; Int{
      for mapItem:MKMapItem in mapItems{
          self.SetAnnotations(mapItem: mapItem)
          if itemsCount == possibleRegionsPerStore { return itemsCount }
          let distanceToUser = CalculateDistanceBetweenTwoCoordinates(location1: userLocation!, location2: mapItem.placemark.coordinate)
          //Monitore 6th nearest stores if regions count still below 20
          if locationManager.monitoredRegions.count &lt; 20 &amp;&amp; distanceToUser &gt;= minDistance &amp;&amp; distanceToUser &lt;= maxDistance {
              MonitoreCircularRegion(mapItem: mapItem)
             return  itemsCount + 1
          }
      }
      return itemsCount
  }
  private func MonitoreCircularRegion(mapItem: MKMapItem){
      DispatchQueue.main.async {
          let region = CLCircularRegion(center: mapItem.placemark.coordinate, radius: CLLocationDistance(self.radiusToMonitore), identifier: "\(UUID().uuidString)\("SB_")\(mapItem.name!)")
          self.locationManager.startMonitoring(for: region)
  
          NSLog("Monitored Regions: \(self.locationManager.monitoredRegions.count)")
          NSLog("MapSpan: \(self.mapSpan)")
          NSLog("Start monitoring for Region: \(region) with Radius \(region.radius)" )
      }
  }
  </code></pre>
  
  <p>I still did not get updates in background when i move outside my mapspan.
  What I also observe is, that I get four times a response of two times loop in my MKLocalSearchRequest each request seem to deliver two responses. Is that correct?</p>