Swift Writing to and Accessing Class Objects - ios

So I am trying to use MapKit to put a series of points into a map. Ultimitely I am going to get a list of datapoints from csv and load them into the Location Class, then read them from the Class as MapView Annotations. This works here individually for plotting 2 points manually ...but I can't figure out how to properly load and access Location class for a number of items (10 Location Points for example)
Here is my class file
import Foundation
import MapKit
class Location: NSObject, MKAnnotation{
let title: String?
let locationName: String
let discipline: String
let coordinate: CLLocationCoordinate2D
init(title: String, locationName: String, lat: String, lon: String){
self.title = title
self.locationName = locationName
let latDouble = (lat as NSString).doubleValue
let lonDouble = (lon as NSString).doubleValue
let latlong = CLLocationCoordinate2D(latitude: latDouble, longitude: lonDouble)
self.discipline = locationName
self.coordinate = latlong
super.init()
}
}
here is ViewController.swift
import UIKit
import MapKit
class ViewController: UIViewController, MKMapViewDelegate {
//var stops = TransitStop[]()
let initialLocation = CLLocation( latitude: 41.880632, longitude: -87.623277)
#IBOutlet weak var mapView: MKMapView!
let regionRadius: CLLocationDistance = 1000
override func viewDidLoad() {
super.viewDidLoad()
let initial = Location(title: "YOU", locationName: "are here", lat: "41.880632", lon: "-87.623277")
let firstStop = Location(title: "ashland", locationName: "ashland", lat: "41.88574", lon: "-87.627835")// Do any additional setup after loading the view, typically from a nib.
centerMapOnLocation(location: initialLocation)
mapView.addAnnotation(initial)
mapView.addAnnotation(firstStop)
}
func centerMapOnLocation(location: CLLocation) {
let coordinateRegion = MKCoordinateRegionMakeWithDistance(location.coordinate, regionRadius * 2.0, regionRadius * 2.0)
mapView.setRegion(coordinateRegion, animated: true)
}
}

Try something like this:
var locationArray: [Location] = []
for line in csvString{
let values:[String] = line.components(separatedBy: ",")
let title = values[0]
let locationName = values[1]
let lat = values[2]
let lng = values[3]
let latD: Double = Double(lat)
let lngD: Double = Double(lng)
let location = Location(title: title, locationName: locationName, lat: latD, lng: lngD)
locationArray.append(location)
}
centerMapOnLocation(location: initialLocation)
addLocationsToMap(locationArray: locationArray)
Make sure you do some validating on the values (Double) before implementing.
Then you can read the location into the map with this function:
func addLocationsToMap(locationArray: [Location]){
for location in locationArray {
mapView.addAnnotation(location)
}
}
Note: I realize the OP doesn't need to explicitly define the variables. I just thought it would be easier to understand.
I assume the csv value can be read from an array perhaps and that the placement of the values in the csv file for 'title', 'loacationName' are well defined.

Related

how to get rid of the 'The API method must be called from the main thread' problem when retrieving data from an API to use them in arrays? Swift

I come to you because I have the following problem:
I work with the 'GoogleMaps' cocoapods and I need to place several markers in a map by using the latitude, longitude and a codeID that I get from an API. I will present you guys 2 cases: the one that works (that uses 3 hard coded arrays mentioned before) and the one that I try to get from the API and that crashes no matter what I do. OK, the first case (the one that works) is this one:
import UIKit
import GoogleMaps
class ViewController: UIViewController {
// MARK: - Constants and variables
let lat: Double = 38.739429 // User's Latitude
let lon: Double = -9.137115 // User's Longitude
let zoom: Float = 15.0
// MARK: - Elements in the storyboard
#IBOutlet weak var googleMap: GMSMapView!
// MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
googleMapsStuff()
}
// MARK: - Google maps method
func googleMapsStuff() {
googleMap.delegate = self
self.googleMap.isMyLocationEnabled = true // User's current position (blue dot on the map)
let arrayLat: [Double] = [38.739, 38.74, 38.741, 38.732, 38.7325, 38.733]
let arrayLon: [Double] = [-9.136, -9.135, -9.134, -9.137, -9.1375, -9.138]
//var arrayCompanyZoneID: [Int] = []
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: lat, longitude: lon, zoom: self.zoom)
googleMap.camera = camera
for index in 0..<arrayLon.count {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: arrayLat[index], longitude: arrayLon[index])
marker.title = "Marker number: \(index)"
marker.snippet = "Marker's Lat: \(arrayLat[index]), Marker's Lon: \(arrayLon[index])"
marker.map = self.googleMap
print("Index: \(index)")
}
}
}
And as you can see in the image, it all goes smoothly well
The problem comes in the second case, when I try to fill the empty arrays (which it seems to do) when I connect to an API to get that data. This is the "failure" case:
struct MyInfo: Codable {
let id: String
let name: String
let x: Double // Longitude
let y: Double // Latitude
let licencePlate: String?
let range: Int?
let batteryLevel: Int?
let seats: Int?
let model: String?
let resourceImageId: String?
let pricePerMinuteParking: Int?
let pricePerMinuteDriving: Int?
let realTimeData: Bool?
let engineType: String?
let resourceType: String?
let companyZoneId: Int
let helmets: Int?
let station: Bool?
let availableResources: Int?
let spacesAvailable: Int?
let allowDropoff: Bool?
let bikesAvailable: Int?
}
class ViewController: UIViewController {
// MARK: - Constants and variables
let lat: Double = 38.739429 // User's Latitude
let lon: Double = -9.137115 // User's Longitude
let zoom: Float = 15.0
var arrayLat: [Double] = [] // [38.7395, 38.739, 38.74, 38.741, 38.732, 38.7325, 38.733]
var arrayLon: [Double] = [] // [-9.1365, -9.136, -9.135, -9.134, -9.137, -9.1375, -9.138]
var arrayCompanyZoneID: [Int] = [] // [1, 2, 3, 4, 5, 6, 7]
// MARK: - Elements in the storyboard
#IBOutlet weak var googleMap: GMSMapView!
// MARK: - ViewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
googleMap.delegate = self
self.googleMap.isMyLocationEnabled = true // User's current position (blue dot on the map)
let camera: GMSCameraPosition = GMSCameraPosition.camera(withLatitude: self.lat, longitude: self.lon, zoom: self.zoom)
googleMap.camera = camera
guard let urlAPI = URL(string: "https://apidev.meep.me/tripplan/api/v1/routers/lisboa/resources?lowerLeftLatLon=38.711046,-9.160096&upperRightLatLon=38.739429,-9.137115") else { return }
let task = URLSession.shared.dataTask(with: urlAPI) {(data, response, error) in
if error == nil {
guard let urlContent = data else { return }
do {
let JSONResult = try JSONDecoder().decode([MyInfo].self, from: urlContent) //JSONSerialization.jsonObject(with: urlContent, options: .mutableContainers)
print("JSON Result:", JSONResult)
for jsonData in JSONResult {
self.arrayLon.append(jsonData.x)
self.arrayLat.append(jsonData.y)
self.arrayCompanyZoneID.append(jsonData.companyZoneId)
}
print("-----------------")
print(type(of: JSONResult))
print("-----------------")
print("ArrayLon:", self.arrayLon)
print("ArrayLat:", self.arrayLat)
print("companyZoneId: ", self.arrayCompanyZoneID)
print("Count zoneid: ", self.arrayCompanyZoneID.count)
print("-----------------")
// MARK: - Place the multiple markers on the map
for index in 0..<self.arrayCompanyZoneID.count {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: self.arrayLat[index], longitude: self.arrayLon[index])
marker.title = "Marker number: \(index)"
marker.snippet = "Marker's Lat: \(self.arrayLat[index]), Marker's Lon: \(self.arrayLon[index])"
marker.map = self.googleMap
print("Index: \(index)")
}
} catch {
print("JSON processing failed.")
}
} else {
print("Error serializing JSON:", error!)
}
}
task.resume()
}
And it doesn't matter what I do, the console always says:
"Terminating app due to uncaught exception 'GMSThreadException', reason: 'The API method must be called from the main thread' "
I also tried using the method
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
but it also says that the API method most be called from the main thread.
I'm so stuck here and I invested several hours to this issue but It only fails over and over.
I appreciate your advice and wisdom.
Thanks in advance.
You need
DispatchQueue.main.async {
// MARK: - Place the multiple markers on the map
for index in 0..<self.arrayCompanyZoneID.count {
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude: self.arrayLat[index], longitude: self.arrayLon[index])
marker.title = "Marker number: \(index)"
marker.snippet = "Marker's Lat: \(self.arrayLat[index]), Marker's Lon: \(self.arrayLon[index])"
marker.map = self.googleMap
print("Index: \(index)")
}
}
As URLSession.shared.dataTask callback is in a background thread

swift 4 Annotation from json not visible until user drags on map

I am having problems showing the annotations on the map. They only show when I move or drag the map.
I tried to follow tutorial on youtube and also one of the questions which is c sharp
Please can sone one help .. here is the code
Here I created a class for the annotation
class customPin: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(pinTitle:String, pinSubTitle:String, location:CLLocationCoordinate2D) {
self.title = pinTitle
self.subtitle = pinSubTitle
self.coordinate = location
}}
class ViewController: UIViewController, CLLocationManagerDelegate {
#IBOutlet weak var mapView: MKMapView!
var locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
self.locationManager.requestWhenInUseAuthorization()
//json show the list of the parks
pipiCanList()
//show location Barcelona
let location = CLLocationCoordinate2D(latitude: 41.3851 , longitude:2.1734)
let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
self.mapView.setRegion(region, animated: true)
self.mapView.delegate = self
}
get the json information
func pipiCanList(){
let url = "http://bluewave.lk/Apps/pipiCan/Api/read.php";
let urlObj = URL(string:url);
URLSession.shared.dataTask(with: urlObj!) {(data, response, error) in
do{
let pipiCans = try JSONDecoder().decode([pipiCanDBList].self, from: data!)
for pipiCan in pipiCans{
let Latitude = (pipiCan.ParkLatitude! as NSString).doubleValue
let Longitude = (pipiCan.ParkLongitude! as NSString).doubleValue
let location = CLLocationCoordinate2D(latitude: Latitude, longitude: Longitude)
//add to
let pin = customPin(pinTitle: pipiCan.ParkName!, pinSubTitle: pipiCan.Area!, location: location)
self.mapView.addAnnotation(pin)
}
} catch{
print ("Error - cannot get list")
}
}.resume()
}
}
You need
DispatchQueue.main.async {
let pin = customPin(pinTitle: pipiCan.ParkName!, pinSubTitle: pipiCan.Area!, location: location)
self.mapView.addAnnotation(pin)
}
As URLSession.shared.dataTask callback is in a background thread and you should access UIKit elements like mapView in the main thread You can also do
self.mapView.showAnnotations(self.mapView.annotations, animated: true)
after the for loop to show all added annotations also keep in mind you set a region here
let location = CLLocationCoordinate2D(latitude: 41.3851 , longitude:2.1734)
let region = MKCoordinateRegion(center: location, span: MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05))
that the response may be with far location values than the above region

include local on map with firebase swift - error

I am encountering great difficulties in putting an integrated map based on firebase in my project, I am looking for expert knowledge to help me, even though I build run correctly at the time of running the system for, my code below:
Thanks
#IBOutlet var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
let locationsRef = Database.database().reference(withPath: "locations")
locationsRef.observe(.value, with: { snapshot in
for item in snapshot.children {
guard let locationData = item as? DataSnapshot else { continue }
var locationValue = locationData.value as! [String: Any]
var location: CLLocationCoordinate2D!
if let lat = locationValue["lat"] as? String {
let lng = Double(locationValue["lng"] as! String)!
location = CLLocationCoordinate2D(latitude: Double(lat)!, longitude: lng)
} else {
let lat = locationValue["lat"] as! Double
let lng = locationValue["lng"] as! Double
location = CLLocationCoordinate2D(latitude: lat, longitude: lng)
}
func addAnnotations(coords: [CLLocation]){
for coord in coords{
let CLLCoordType = CLLocationCoordinate2D(latitude: coord.coordinate.latitude,
longitude: coord.coordinate.longitude);
let anno = MKPointAnnotation();
anno.coordinate = CLLCoordType;
self.mapView.addAnnotation(anno);
}
}
}
})
}

how can I create objects of my class in swift based on json fetched by swiftyJson?

I have a class as follows:
import Foundation
import MapKit
class SingleRequest: NSObject, MKAnnotation {
var title: String?
let created: String
let discipline: String
let coordinate: CLLocationCoordinate2D
var items = NSMutableArray()
init(title: String, created: String, discipline: String, coordinate: CLLocationCoordinate2D) {
self.title = title
self.created = created
self.discipline = discipline
self.coordinate = coordinate
super.init()
}
}
I also have a json that looks like:
[{"_id":"56c9d44fbcb42e075f7d49b1",
"username":"Ms. Brynlee Quitzon DDS",
"photo":"photo.jpg",
"number":"one",
"description":"Maiores rerum beatae molestiae autem. Voluptatem magni aspernatur est voluptas.",
"__v":0,
"updated_at":"2016-02-21T15:14:23.123Z",
"created_at":"2016-02-21T15:14:23.116Z",
"location":{
"type":"Point",
"coordinates":[5.300567929507009,44.04127433959841]}
},
etc.
and now I want to fetch all json entries and create SingleRequest object for each of them.
So far I created a method in this class:
class func getAllRequests() {
print("getAllRequests")
RestApiManager.sharedInstance.getRequests { json in
let results = json//["username"]
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
var title = user["description"]
let created = user["created_at"]
let discipline = user["number"]
let latitude = (user[""]).doubleValue
let longitude = (user[""]).doubleValue
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
return SingleRequest(title: title, created: created!, discipline: discipline!, coordinate: coordinate)
}
}
}
And now 2 questions:
1) as you can see above, I left those two fields empty:
let latitude = (user[""]).doubleValue
let longitude = (user[""]).doubleValue
that's because I don't know how to refer to the long/lat values from my json, since they are embeded in the coordinates field...
How can I fill it?
2) will this function create needed objects? or should I for example change the declaration to mark some return value:
class func getAllRequests()
? Thanks!
For your first question, you need to first get the array out of user["coordinates"] and then downcast it to Array, user["coordinates"] as? Array<Double>
For your second question, it should return an array of SingleRequest, Array<SingleRequest>
class func getAllRequests() -> Array<SingleRequest> {
var requests: Array<SingleRequest> = []
RestApiManager.sharedInstance.getRequests { json in
let results = json//["username"]
for (index: String, subJson: JSON) in results {
let user: AnyObject = JSON.object
var title = user["description"]
let created = user["created_at"]
let discipline = user["number"]
guard let coordinates = user["coordinates"] as? Array<Double> else { print("no lat/long") }
let latitude = coordinates[0]
let longitude = coordinates[1]
let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
requests.append(SingleRequest(title: title, created: created!, discipline: discipline!, coordinate: coordinate))
}
}
return requests
}
var singleRequestsArray = [SingleRequest]()
class func getAllRequests() {
RestApiManager.sharedInstance.getRequests { json in
for (index: String, subJson: JSON) in son {
let user: AnyObject = subjson.object
let title = user["description"]
let created = user["created_at"]
let discipline = user["number"]
var coordinate = CLLocationCoordinate2D()
if let coordinates = user["coordinates"].array{
let latitude = coordinates.first?).doubleValue
let longitude = coordinates.array.last?).doubleValue
coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
singleRequestsArray.append(SingleRequest(title: title, created: created!, discipline: discipline!, coordinate: coordinate))
}
}
}
Hope this will help you!

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)")
}
}
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.
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.
Multiple 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
}
}

Resources