Hunter
Hunter

Reputation: 421

Swift: Geofencing / geolocations near user location

Problem:

I'm trying to make it so that it uses the user's location all the time and constantly checks to see if it is within 5 miles of the CLLocationCoordinate2D points I have set. If so, it sends a alert if the app is open or a notification if the app is closed.

Useful Information:

In my project I have 4 swift files:

Locations.swift holds the CLLocationCoordinate2D points.

Utilities.swift holds a simple alert.

UserLocation.swift retrieves and updates the user's location

GeoLocationViewController.swift monitors the locations

Note:

Some code may not be in the right place or relevant, I was looking at another project trying to extract relevant code from it to use in my project. Please tell me if you do catch a mistake.

Code:

Locations.swift:

import UIKit
import MapKit

class Locations: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        var radius: CLLocationDistance = 5        
        let arroyo = CLLocationCoordinate2D (latitude: 33.781327997137595, longitude: -116.46394436519012)
        var arroyoCoord: CLLocationCoordinate2D = arroyo

        let buddyrogers = CLLocationCoordinate2D (latitude: 33.78051204742721, longitude: -116.46362250010833)
        var buddyCoord: CLLocationCoordinate2D = buddyrogers

        let chopsticks = CLLocationCoordinate2D (latitude: 33.815995425565184, longitude: -116.44107442645873)
        let colorfulfountain = CLLocationCoordinate2D (latitude: 33.80443304398751, longitude: -116.45723923544313)
        let diamond = CLLocationCoordinate2D (latitude: 33.80216859530781, longitude: -116.45711048941041)
        let dinahshore = CLLocationCoordinate2D (latitude: 33.806554795852996, longitude: -116.47734507421876)
        let fountoflife = CLLocationCoordinate2D (latitude: 33.78075282028137, longitude: -116.46407847564086)
        let fountains = CLLocationCoordinate2D (latitude: 33.780141969313235, longitude: -116.46346156756744)
        let historicphoto = CLLocationCoordinate2D (latitude: 33.78130570353292, longitude: -116.46389072100982)
        let holistic = CLLocationCoordinate2D (latitude: 33.781338029257775, longitude: -116.46408249895438)
        let hollywoodheroes = CLLocationCoordinate2D (latitude: 33.78095792254918, longitude: -116.45820483068849)
        let indiangathering = CLLocationCoordinate2D (latitude: 33.78136366689296, longitude: -116.46371905963287)
        let indianwomen = CLLocationCoordinate2D (latitude: 33.78622660767695, longitude: -116.45820483068849)
        let cathedrals = CLLocationCoordinate2D (latitude: 33.844502990031124, longitude: -116.45834321534426)
        let firehouse = CLLocationCoordinate2D (latitude: 33.78103817982461, longitude: -116.46700744788512)
        let perfectunion = CLLocationCoordinate2D (latitude: 33.778193459376865, longitude: -116.45877843062743)
        let lizards = CLLocationCoordinate2D (latitude: 33.78104263855992, longitude: -116.46340792338714)
        let cactus = CLLocationCoordinate2D (latitude: 33.782598723009976, longitude: -116.46699671904906)
        let swisscheese = CLLocationCoordinate2D (latitude: 33.78121541437478, longitude: -116.46472086469993)
        let newbeginning = CLLocationCoordinate2D (latitude: 33.78049421237406, longitude: -116.46463101069793)
        let thunderbolt = CLLocationCoordinate2D (latitude: 33.80140187863324, longitude: -116.46646603445436)
        let tictoc = CLLocationCoordinate2D (latitude: 33.80156235478469, longitude: -116.45524367193605)
        let wheeloftime = CLLocationCoordinate2D (latitude: 33.815987530910135, longitude: -116.45892863433227)         
        let artevita = CLLocationCoordinate2D (latitude: 33.7826633, longitude: -116.46041969999999)
        let coachellaart = CLLocationCoordinate2D (latitude: 33.78012700000001, longitude: -116.46571840000001)
        let colinfisher = CLLocationCoordinate2D (latitude: 33.7819228, longitude: -116.46002010000001)
        let garycreative = CLLocationCoordinate2D (latitude: 33.782660, longitude: -116.462141)
        let lesliejean = CLLocationCoordinate2D (latitude: 33.78404799999999, longitude: -116.4635222)
        let rebeccafine = CLLocationCoordinate2D (latitude: 33.782487, longitude: -116.460564)        
        let agnes = CLLocationCoordinate2D (latitude: 33.77571242620008, longitude: -116.46372063254091)
       let willardprice = CLLocationCoordinate2D (latitude: 33.77489419346815, longitude: -116.46667910908434)
       let adobe = CLLocationCoordinate2D (latitude: 33.77479870632753, longitude: -116.46673050629039)
        let valsamuelson = CLLocationCoordinate2D (latitude: 33.76802162366799, longitude: -116.46920998147584)
        let gallito = CLLocationCoordinate2D (latitude: 33.7794358, longitude: -116.4612692)        
        let townsquare = CLLocationCoordinate2D (latitude: 33.7810365, longitude: -116.46464559999998)
        let ocotillo = CLLocationCoordinate2D (latitude: 33.805963, longitude: -116.46349980000002)
        let century = CLLocationCoordinate2D (latitude: 33.8269913, longitude: -116.4424588)
        let denniskeat = CLLocationCoordinate2D (latitude: 33.8304982, longitude: -116.45744730000001)
        let memorial = CLLocationCoordinate2D (latitude: 33.78318512716751, longitude: -116.46681405767208)
        let patriot = CLLocationCoordinate2D (latitude: 33.8019902897174, longitude: -116.44000872473146)
        let panorama = CLLocationCoordinate2D (latitude: 33.83861734636407, longitude: -116.46799619895023)
        let secondst = CLLocationCoordinate2D (latitude: 33.78069442561766, longitude: -116.45910418200071)
        let dogpark = CLLocationCoordinate2D (latitude: 33.7804269, longitude: -116.46041309999998)


    }

}

Utilities.swift:

import UIKit
import MapKit

func showSimpleAlertWithTitle(title: String!, message: String, viewController: UIViewController) {
    let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
    let action = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
    alert.addAction(action)
    viewController.presentViewController(alert, animated: true, completion: nil)
}

UserLocation.swift:

import UIKit
import CoreLocation

class UserLocation: UIViewController, CLLocationManagerDelegate {
    var locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        if (CLLocationManager.locationServicesEnabled()) {
            locationManager = CLLocationManager()
            locationManager.delegate = self
            locationManager.desiredAccuracy = kCLLocationAccuracyBest
            locationManager.requestAlwaysAuthorization()
            locationManager.startUpdatingLocation()
        }

        locationManager.delegate = self
        locationManager.requestAlwaysAuthorization()

    }

}

GeoLocationViewController.swift:

import UIKit
import CoreLocation

class GeoLocationViewController: UIViewController, CLLocationManagerDelegate {

    let locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        locationManager.delegate = self
        locationManager.requestAlwaysAuthorization()

        // Do any additional setup after loading the view.
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func regionWithGeotification(geotification: Locations) -> CLCircularRegion {
        // 1
        let region = CLCircularRegion(center: geotification.coordinate, radius: geotification.radius, identifier: geotification.identifier)
        // 2
        region.notifyOnEntry = (geotification.eventType == .OnEntry)
        region.notifyOnExit = !region.notifyOnEntry
        return region
    }

    func startMonitoringGeotification(geotification: Locations) {
        // 1
        if !CLLocationManager.isMonitoringAvailableForClass(CLCircularRegion) {
            showSimpleAlertWithTitle("Error", message: "Geofencing is not supported on this device!", viewController: self)
            return
        }
        // 2
        if CLLocationManager.authorizationStatus() != .AuthorizedAlways {
            showSimpleAlertWithTitle("Warning", message: "Your geotification is saved but will only be activated once you grant permission to access the device location.", viewController: self)
        }
        // 3
        let region = regionWithGeotification(geotification)
        // 4
        locationManager.startMonitoringForRegion(region)
    }

    func stopMonitoringGeotification(geotification: Locations) {
        for region in locationManager.monitoredRegions {
            if let circularRegion = region as? CLCircularRegion {
                if circularRegion.identifier ==  geotification.identifier {
                    locationManager.stopMonitoringForRegion(circularRegion)
                }
            }
        }
    }

    func locationManager(manager: CLLocationManager, monitoringDidFailForRegion region: CLRegion?, withError error: NSError) {
        print("Monitoring failed for region with identifier: \(region!.identifier)")
    }

    func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
        print("Location Manager failed with the following error: \(error)")
    }

}

Upvotes: 5

Views: 6475

Answers (3)

Karan Vakharia
Karan Vakharia

Reputation: 87

enter image description hereHere is GeoFenceMultiple GeoFencing with AppleMap In Xcode 12.3(Swift 5) 100% Working

import UIKit
import MapKit
import CoreLocation

struct GeotificationData {
   var lat : String?
   var long : String?
}

class AppleMapVC: UIViewController {

   @IBOutlet weak var mapView: MKMapView!

   lazy var locationManager = CLLocationManager()
   var arrGeoFenceData = [GeotificationData]()


   //MARK:- VIEW CONTROLLER LIFE CYCLE METHOD

   override func viewDidLoad() {
       super.viewDidLoad()
       title = "GeoFence"
       UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { (granted, error) in }
       if CLLocationManager.locationServicesEnabled() {
           locationManager.delegate = self
           locationManager.requestAlwaysAuthorization()
           locationManager.desiredAccuracy = kCLLocationAccuracyBest //optimize power performanc Battery
           locationManager.startUpdatingLocation()
           arrGeoFenceData = [GeotificationData(lat: "21.7469", long: "74.1240"),
                           GeotificationData(lat: "21.1702", long: "72.8311"),
                           GeotificationData(lat: "19.9975", long: "73.7898"),
                           GeotificationData(lat: "20.1738", long: "72.7640"),
                           GeotificationData(lat: "19.0760", long: "72.8777"),
                           GeotificationData(lat: "18.5204", long: "73.8567")]
       }
       getGeoFencing()
    
   }

   //show notification

   func showNotification(title:String, message:String) {
       let content = UNMutableNotificationContent()
       content.title = title
       content.body = message
       content.badge = 1
       content.sound = .default
       let request = UNNotificationRequest(identifier: "notifi", content: content, trigger: nil)
       UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
   }
   func monitorRegionAtLocation(center: CLLocationCoordinate2D, identifier: String ) {
       // Make sure the devices supports region monitoring.
       if CLLocationManager.isMonitoringAvailable(for: CLCircularRegion.self) {
           // Register the region.
           let maxDistance = CLLocationDistance(30000)
           let region = CLCircularRegion(center: center,
             radius: maxDistance, identifier: identifier)
           region.notifyOnEntry = true
           region.notifyOnExit = false
           let circle = MKCircle(center: center, radius: maxDistance)
           mapView.addOverlay(circle)
           locationManager.startMonitoring(for: region)
        
       }
   }


   func getGeoFencing() {
       for item in arrGeoFenceData {
           print("Your location with lat and long :- \(item)")
           let cordi = CLLocationCoordinate2D(latitude: Double(item.lat!)!, longitude: Double(item.long!)!)
           monitorRegionAtLocation(center: cordi, identifier: "Geofence")
       }
   }

   //MARK:- UIBUTTON CLICKED

   @IBAction func btnRotationClicked(_ sender: Any) {
       mapView.setUserTrackingMode(.followWithHeading, animated: true)
   }

}

//MARK:- LOCATIONMANAGER DELEGATE METHOD FOR UPDATE LOCATION

extension AppleMapVC : CLLocationManagerDelegate {
   func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
       if let location = locations.first {
           locationManager.stopUpdatingLocation()
  //               locationManager.startUpdatingLocation()
           render(location)
       }
   }

   func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
       let alert = UIAlertController.init(title: "You enter in location", message: "enter in geofence", preferredStyle: .alert)
       alert.addAction(UIAlertAction(title: "Continue", style: UIAlertAction.Style.default, handler: nil))
    
       self.present(alert, animated: true, completion: nil)
       showNotification(title: "You entered in geofence", message: "Welcome")
   }
   func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
       let alert = UIAlertController.init(title: "You exit in location", message: "exit in geofence", preferredStyle: .alert)
       alert.addAction(UIAlertAction(title: "Continue", style: UIAlertAction.Style.default, handler: nil))
    
       self.present(alert, animated: true, completion: nil)
       showNotification(title: "You exit in geofence", message: "come again")
   }

   func render(_ location: CLLocation) {
       let coordinate = CLLocationCoordinate2D(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
       let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1)
       let region = MKCoordinateRegion(center: coordinate, span: span)
       mapView.setRegion(region, animated: true)
       mapView.showsUserLocation = true
//        let pin = MKPointAnnotation()
//        pin.coordinate = coordinate
//        mapView.addAnnotation(pin)
   }
}

 //MARK:- MKMAPVIEW DELEGATE METHOD

extension AppleMapVC : MKMapViewDelegate {
   func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
       guard let circleOverlay = overlay as? MKCircle else {
        return MKOverlayRenderer()
       }
       let circleRender = MKCircleRenderer(circle: circleOverlay)
       circleRender.strokeColor = .red
       circleRender.fillColor = .red
       circleRender.alpha = 0.4
       return circleRender
   }
}

Upvotes: 0

olympia
olympia

Reputation: 352

First and foremost, change back startMonitoringGeotification(), regionWithGeotification(), and stopMonitoringGeotification() to take in a Geotification like the Ray Wenderlich tutorial. Make sure you have added the file Geotification.swift from his starter code to your project.

Also, make sure your Main.storyboard launches your ViewController. Without this step, none of your code will run.

1) redefine your Locations class more simply in Locations.swift:

import UIKit
import MapKit

class Locations {

  static let locations:[String:CLLocationCoordinate2D] = [
    "buddyrogers" : CLLocationCoordinate2D(latitude: 33.815995425565184, longitude: -116.44107442645873),
    "diamond"     : CLLocationCoordinate2D(latitude: 33.802168595307814, longitude: -116.45711048941041),
     . 
     .    // add your locations
     .
    ]
  }
}

like the @hungry-yeti suggested

2) You can define showSimpleAlertWithTitle() in your GeotificationViewController class. Try calling it in your ViewDidLoad() to test it. You can now delete Utilities.swift.

3) I think you can ignore/remove UserLocation.swift, this seems unnecessary

4) Put this code inside GeotificationViewController's ViewDidLoad:

let radius = CLLocationDistance(8046.72) // 5 miles in meters

for location in Locations.locations {
  let g = Geotification(coordinate: location.1, radius: radius, identifier: location.0, note: "test", eventType: EventType.OnEntry)
    startMonitoringGeotification(g)
}

5) I hope this helps and simplifies your code. Party on, reply here if you have any issues.

Upvotes: 4

hungri-yeti
hungri-yeti

Reputation: 584

It looks like you're using the Ray Wenderlich tutorial. That's a good one, I found it very useful too.

First off, the unit for CLLocationDistance is meters so the code you have specifies a radius of 5 meters which won't be quite as useful as you may hope; a value of 8046.72 is closer to 5 miles.

Regarding the specific error, Locations is the class that you stuffed all CLLocationCoordinate2D values in, it certainly does not have any member called coordinate. If you're using the tutorial I'm thinking of you will need to load those coords into instances of the Geotification class.

Here is some untested code:

  // Load the various coords into an array:
  var locations:[(note:String, coords:CLLocationCoordinate2D)] = []
  locations +=[(note: "arroyo", CLLocationCoordinate2D( latitude: 33.781327997137595, longitude: -116.46394436519012)]
  locations +=[(note: "buddyrogers", CLLocationCoordinate2D( latitude: 33.78051204742721, longitude: -116.46362250010833)]
  // ...

  let radius = 8000  // ~5 miles rounded to nearest km
  // Load the locations into geotifications:
  for location in locations {
     let geotification = Geotification(coordinate: location.cords, radius: radius, identifier: NSUUID().UUIDString, note: location.note, eventType: EventType.OnEnter)
     startMonitoringGeotification(geotification)
  }

Now bear in mind that there is a hard limit of 20 monitored regions per app, so if you have more than that you will need to dynamically determine the nearest 20 regions and then monitor those regions.

Upvotes: 1

Related Questions