diff --git a/GeofenceTester.xcodeproj/project.pbxproj b/GeofenceTester.xcodeproj/project.pbxproj index a9ec2ba..a815e8a 100644 --- a/GeofenceTester.xcodeproj/project.pbxproj +++ b/GeofenceTester.xcodeproj/project.pbxproj @@ -7,17 +7,18 @@ objects = { /* Begin PBXBuildFile section */ + CB268BA0285A19DC00B16C3A /* PersistantStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB268B9F285A19DC00B16C3A /* PersistantStorage.swift */; }; CB655F6628534E2600EBCABA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB655F6528534E2600EBCABA /* AppDelegate.swift */; }; CB655F6828534E2600EBCABA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB655F6728534E2600EBCABA /* SceneDelegate.swift */; }; CB655F6A28534E2600EBCABA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB655F6928534E2600EBCABA /* ViewController.swift */; }; CB655F6D28534E2600EBCABA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB655F6B28534E2600EBCABA /* Main.storyboard */; }; CB655F6F28534E2700EBCABA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CB655F6E28534E2700EBCABA /* Assets.xcassets */; }; CB655F7228534E2700EBCABA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CB655F7028534E2700EBCABA /* LaunchScreen.storyboard */; }; - CB655F7C2856854E00EBCABA /* RegionsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB655F7B2856854E00EBCABA /* RegionsTableViewController.swift */; }; CB655F7E2856A43B00EBCABA /* RegionsDetailControllerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB655F7D2856A43B00EBCABA /* RegionsDetailControllerViewController.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + CB268B9F285A19DC00B16C3A /* PersistantStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistantStorage.swift; sourceTree = ""; }; CB655F6228534E2600EBCABA /* GeofenceTester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GeofenceTester.app; sourceTree = BUILT_PRODUCTS_DIR; }; CB655F6528534E2600EBCABA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; CB655F6728534E2600EBCABA /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -26,7 +27,6 @@ CB655F6E28534E2700EBCABA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; CB655F7128534E2700EBCABA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; CB655F7328534E2700EBCABA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - CB655F7B2856854E00EBCABA /* RegionsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsTableViewController.swift; sourceTree = ""; }; CB655F7D2856A43B00EBCABA /* RegionsDetailControllerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionsDetailControllerViewController.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -63,8 +63,8 @@ CB655F6528534E2600EBCABA /* AppDelegate.swift */, CB655F6728534E2600EBCABA /* SceneDelegate.swift */, CB655F6928534E2600EBCABA /* ViewController.swift */, - CB655F7B2856854E00EBCABA /* RegionsTableViewController.swift */, CB655F7D2856A43B00EBCABA /* RegionsDetailControllerViewController.swift */, + CB268B9F285A19DC00B16C3A /* PersistantStorage.swift */, CB655F6B28534E2600EBCABA /* Main.storyboard */, CB655F6E28534E2700EBCABA /* Assets.xcassets */, CB655F7028534E2700EBCABA /* LaunchScreen.storyboard */, @@ -146,9 +146,9 @@ files = ( CB655F6A28534E2600EBCABA /* ViewController.swift in Sources */, CB655F6628534E2600EBCABA /* AppDelegate.swift in Sources */, - CB655F7C2856854E00EBCABA /* RegionsTableViewController.swift in Sources */, CB655F6828534E2600EBCABA /* SceneDelegate.swift in Sources */, CB655F7E2856A43B00EBCABA /* RegionsDetailControllerViewController.swift in Sources */, + CB268BA0285A19DC00B16C3A /* PersistantStorage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/GeofenceTester/Base.lproj/Main.storyboard b/GeofenceTester/Base.lproj/Main.storyboard index 617d6c8..6bccb3f 100644 --- a/GeofenceTester/Base.lproj/Main.storyboard +++ b/GeofenceTester/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -17,7 +18,7 @@ - + @@ -89,37 +90,30 @@ - - + + - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + - + @@ -129,8 +123,8 @@ - - + + @@ -139,20 +133,21 @@ - - - - + + + + + - + @@ -173,10 +168,13 @@ - + + + + diff --git a/GeofenceTester/Info.plist b/GeofenceTester/Info.plist index e5574ff..4c20630 100644 --- a/GeofenceTester/Info.plist +++ b/GeofenceTester/Info.plist @@ -27,6 +27,11 @@ NSLocationAlwaysAndWhenInUseUsageDescription This is a region monitoring testing app. It needs background location updates + UIRequiredDeviceCapabilities + + gps + location-services + NSLocationWhenInUseUsageDescription In Use will not do the trick diff --git a/GeofenceTester/PersistantStorage.swift b/GeofenceTester/PersistantStorage.swift new file mode 100644 index 0000000..2e6f5a6 --- /dev/null +++ b/GeofenceTester/PersistantStorage.swift @@ -0,0 +1,61 @@ +// +// PersistantStorage.swift +// GeofenceTester +// +// Created by Alexander von Below on 15.06.22. +// + +import Foundation + + + +class PersistantStorage { + + private var storage = Array() + private let decoder = JSONDecoder() + private let encoder = JSONEncoder() + private let kFilename = "geolocator.log" + + init() throws { + let url = try fileURL() + let jsonData: Data + do { + jsonData = try Data(contentsOf: url) + } + catch { + // File does not exist + return + } + storage = try decoder.decode([T].self, from: jsonData) + } + + func store(_ object: T) throws { + storage.append(object) + try save() + } + + func retrieveAll() -> [T] { + return storage + } + + private func fileURL() throws -> URL { + let fm = FileManager.default + var path: URL! + do { + path = try fm.url(for: .documentDirectory, + in: .userDomainMask, + appropriateFor: nil, + create: false) + } + catch { + throw (error) + } + path.appendPathComponent(kFilename) + return path + } + + private func save() throws { + let json = try encoder.encode(storage) + try json.write(to: fileURL()) + } +} diff --git a/GeofenceTester/RegionsDetailControllerViewController.swift b/GeofenceTester/RegionsDetailControllerViewController.swift index a1896ac..e9cd29f 100644 --- a/GeofenceTester/RegionsDetailControllerViewController.swift +++ b/GeofenceTester/RegionsDetailControllerViewController.swift @@ -11,27 +11,75 @@ import MapKit class RegionsDetailControllerViewController: UIViewController, MKMapViewDelegate { @IBOutlet var mapView: MKMapView! - var region: CLRegion? { + var regions: Set? { didSet { - self.updateMap() + // self.updateMap() } } override func viewDidLoad() { super.viewDidLoad() - + + self.title = "Monitored Regions" + + registerMapAnnotationViews() + updateMap() } + let defaultReuseIdentifier = "defaultAnnotationView" + private func registerMapAnnotationViews() { + mapView.register(MKMarkerAnnotationView.self, + forAnnotationViewWithReuseIdentifier: defaultReuseIdentifier) +// mapView.register(MKAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(SanFranciscoAnnotation.self)) + } + func updateMap() { guard mapView != nil else { return } - if let region = region as? CLCircularRegion { - let overlay = MKCircle(center: region.center, radius: region.radius) - mapView.addOverlay(overlay) + guard let regions = self.regions else { + return + } + + var mapAnnotations = [MKAnnotation]() + + for region in regions { + if let region = region as? CLCircularRegion { +// let overlay = MKCircle(center: region.center, radius: region.radius) +// mapView.addOverlay(overlay) + let annotation = MKPointAnnotation() + annotation.title = region.identifier + annotation.coordinate = region.center + mapAnnotations.append(annotation) + } } + mapView.addAnnotations(mapAnnotations) + mapView.showAnnotations(mapAnnotations, animated: true) + } + + func coordinate(from coordinate: CLLocationCoordinate2D, latitudeDistance: CLLocationDistance, longitudeDistance: CLLocationDistance) -> CLLocationCoordinate2D { + let region = MKCoordinateRegion(center: coordinate, latitudinalMeters: latitudeDistance, longitudinalMeters: longitudeDistance) + + let newLatitude = coordinate.latitude + region.span.latitudeDelta + let newLongitude = coordinate.longitude + region.span.longitudeDelta + + return CLLocationCoordinate2D(latitude: newLatitude, longitude: newLongitude) + } + + func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { + guard !annotation.isKind(of: MKUserLocation.self) else { + // Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize. + return nil + } + let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: defaultReuseIdentifier, + for: annotation) + annotationView.canShowCallout = true + let rightButton = UIButton(type: .detailDisclosure) + annotationView.rightCalloutAccessoryView = rightButton + + return annotationView } func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer { @@ -42,6 +90,16 @@ class RegionsDetailControllerViewController: UIViewController, MKMapViewDelegate return renderer } + func mapView(_ mapView: MKMapView, + annotationView view: MKAnnotationView, + calloutAccessoryControlTapped control: UIControl){ + let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"), style: .default, handler: { _ in + NSLog("The \"OK\" alert occured.") + })) + self.present(alert, animated: true, completion: nil) + } + /* // MARK: - Navigation diff --git a/GeofenceTester/RegionsTableViewController.swift b/GeofenceTester/RegionsTableViewController.swift deleted file mode 100644 index 9d3b1f0..0000000 --- a/GeofenceTester/RegionsTableViewController.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// RegionsTableViewController.swift -// GeofenceTester -// -// Created by Alexander von Below on 12.06.22. -// - -import UIKit -import CoreLocation - -class RegionsTableViewController: UITableViewController { - - var regions = [CLRegion]() { - didSet { - self.updateTitle() - } - } - - override func viewDidLoad() { - super.viewDidLoad() - - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem - } - - func updateTitle() { - self.title = "\(regions.count) Regions" - } - - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections - return 1 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows - return regions.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "RegionCell", for: indexPath) - - cell.textLabel?.text = regions[indexPath.row].identifier - - return cell - } - - /* - // Override to support conditional editing of the table view. - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the specified item to be editable. - return true - } - */ - - /* - // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - // Delete the row from the data source - tableView.deleteRows(at: [indexPath], with: .fade) - } else if editingStyle == .insert { - // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view - } - } - */ - - /* - // Override to support rearranging the table view. - override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { - - } - */ - - /* - // Override to support conditional rearranging of the table view. - override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the item to be re-orderable. - return true - } - */ - - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if let detailController = segue.destination as? RegionsDetailControllerViewController { - if let row = self.tableView.indexPathForSelectedRow?.row { - detailController.region = regions[row] - } - } - } - -} diff --git a/GeofenceTester/ViewController.swift b/GeofenceTester/ViewController.swift index d829267..839d6c2 100644 --- a/GeofenceTester/ViewController.swift +++ b/GeofenceTester/ViewController.swift @@ -179,10 +179,8 @@ class ViewController: UIViewController, CLLocationManagerDelegate { // Navigation Handling override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if let regionsController = segue.destination as? RegionsTableViewController { - regionsController.regions = locationManager.monitoredRegions.sorted(by: { a, b in - a.identifier > b.identifier - }) + if let regionsController = segue.destination as? RegionsDetailControllerViewController { + regionsController.regions = locationManager.monitoredRegions } } } diff --git a/MyPlayground.playground/Contents.swift b/MyPlayground.playground/Contents.swift new file mode 100644 index 0000000..0cfc018 --- /dev/null +++ b/MyPlayground.playground/Contents.swift @@ -0,0 +1,110 @@ +//: A MapKit based Playground + +import MapKit +import PlaygroundSupport + + +func boundingCoordinates(_ input: [CLLocationCoordinate2D]) -> MKCoordinateRegion { + + var minLat: CLLocationDegrees? + var maxLat: CLLocationDegrees? + var minLon: CLLocationDegrees? + var maxLon: CLLocationDegrees? + + for coordinate in input { + let latitude = coordinate.latitude + if let minValue = minLat { + if latitude < minValue { + minLat = latitude + } + } else { + minLat = latitude + } + + if let maxValue = maxLat { + if latitude > maxValue { + maxLat = latitude + } + } else { + maxLat = latitude + } + + let longitude = coordinate.longitude + if let minValue = minLon { + if longitude < minValue { + minLon = longitude + } + } else { + minLon = longitude + } + if let maxValue = maxLon { + if longitude > maxValue { + maxLon = longitude + } + } else { + maxLon = longitude + } + } + var result = MKCoordinateRegion() + + guard let minLat = minLat, + let maxLat = maxLat, + let minLon = minLon, + let maxLon = maxLon else { + return result + } + + let latitudeSpan = maxLat - minLat + let longitudeSpan = maxLon - minLon + let latMid = latitudeSpan/2 + minLat + let lonMid = longitudeSpan/2 + minLon + result.center = CLLocationCoordinate2DMake(latMid, lonMid) + result.span.longitudeDelta = latitudeSpan + result.span.longitudeDelta = longitudeSpan + return result +} + +let appleParkWayCoordinates = CLLocationCoordinate2DMake(37.334922, -122.009033) + +let kanneCoordinates = CLLocationCoordinate2DMake(37.271082, -121.967619) + +let goldenGateCoordinates = CLLocationCoordinate2DMake(37.819724, -122.478557) + +let coordinates = [appleParkWayCoordinates, kanneCoordinates, goldenGateCoordinates] + +// Now let's create a MKMapView +let mapView = MKMapView(frame: CGRect(x:0, y:0, width:800, height:800)) + +// Define a region for our map view +var mapRegion = MKCoordinateRegion() + +let mapRegionSpan = 0.02 +mapRegion.center = appleParkWayCoordinates +mapRegion.span.latitudeDelta = mapRegionSpan +mapRegion.span.longitudeDelta = mapRegionSpan + + mapRegion = boundingCoordinates(coordinates) +mapView.setRegion(mapRegion, animated: true) + +// Create a map annotation +let annotationKanne = MKPointAnnotation() +annotationKanne.coordinate = kanneCoordinates +annotationKanne.title = "Kanne Home" +annotationKanne.subtitle = "" + +mapView.addAnnotation(annotationKanne) + +let annotationGG = MKPointAnnotation() +annotationGG.coordinate = goldenGateCoordinates +annotationGG.title = "Golden Gate Bridge" +mapView.addAnnotation(annotationGG) + +let annotation = MKPointAnnotation() +annotation.coordinate = appleParkWayCoordinates +annotation.title = "Apple Inc." +annotation.subtitle = "One Apple Park Way, Cupertino, California." + +mapView.addAnnotation(annotation) + +// Add the created mapView to our Playground Live View +PlaygroundPage.current.liveView = mapView diff --git a/MyPlayground.playground/contents.xcplayground b/MyPlayground.playground/contents.xcplayground new file mode 100644 index 0000000..cf026f2 --- /dev/null +++ b/MyPlayground.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata b/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..ca3329e --- /dev/null +++ b/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + +