MapKit adding multiple annotations from core data - swift

This is my code. It loops for the number records in the database but only retrieves the first records lat and lon.
func fetch() {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Mappoints")
let fetchResults = try! context.executeFetchRequest(freq) as! [NSManagedObject]
self.mapView.delegate = self
myData = fetchResults
myData.count
for _ in myData {
let data: NSManagedObject = myData[row]
lat = (data.valueForKey("latitude") as? String)!
lon = (data.valueForKey("longitude") as? String)!
let latNumb = (lat as NSString).doubleValue
let longNumb = (lon as NSString).doubleValue
let signLocation = CLLocationCoordinate2DMake(latNumb, longNumb)
addAnnotaion(signLocation)
}
}
I am sure I am missing something simple but just keep missing it.

Your loop looks weird. You say myData[row], but you don't seem to increment the row. If the row does not increment, the data variable will always be the same.
You could do for example for data in myData { ...

This is the code I ended up with to fix the issue.
func fetch() {
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Mappoints")
let fetchResults = try! context.executeFetchRequest(freq) as! [NSManagedObject]
self.mapView.delegate = self
myData = fetchResults
let ct = myData.count // Add this line
// Then changed the for statement from for _ in myData
// To the line below and now all map points show up.
for row in 0...ct-1 {
let data: NSManagedObject = myData[row]
lat = (data.valueForKey("latitude") as? String)!
lon = (data.valueForKey("longitude") as? String)!
let latNumb = (lat as NSString).doubleValue
let longNumb = (lon as NSString).doubleValue
let signLocation = CLLocationCoordinate2DMake(latNumb, longNumb)
addAnnotaion(signLocation)
}

Related

Retrieve data from Firebase before adding to a map

I'm building an application where a user can store companies information (like name, address, latitude, longitude to a Firebase real-time database).
With that information, those companies are then presented in a map as annotations.
My viewDidLoad is shown below
override func viewDidLoad() {
super.viewDidLoad()
// Definitions
let autenticacao = Auth.auth()
let idUsuarioLogado = (autenticacao.currentUser?.uid)!
let database = Database.database().reference()
let usuarios = database.child("usuarios")
let clinicas = database.child("clinicas")
// Map - User location
self.mapa.delegate = self
self.gerenciadorLocalizacao.delegate = self
self.gerenciadorLocalizacao.desiredAccuracy = kCLLocationAccuracyBest
self.gerenciadorLocalizacao.requestWhenInUseAuthorization()
self.gerenciadorLocalizacao.startUpdatingLocation()
// retrieve user information
usuarios.child(idUsuarioLogado).observe(DataEventType.value) { (snapshot) in
let dados = snapshot.value as? NSDictionary
let emailUsuario = dados?["email"] as! String
let nomeUsuario = dados?["nome"] as! String
let perfilUsuario = dados?["perfil"] as! String
let idUsuario = snapshot.key
let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
print("User profile \(perfilUsuario)")
}
// Clinicas listeners
clinicas.observe(DataEventType.childAdded) { (snapshot) in
let dados = snapshot.value as? NSDictionary
//print("Dados na leitura \(dados)")
let clinica = Clinica()
clinica.identificador = snapshot.key
clinica.nome = dados?["nome"] as! String
clinica.endereco = dados?["endereco"] as! String
clinica.cidade = dados?["cidade"] as! String
clinica.cep = dados?["cep"] as! String
clinica.estado = dados?["estado"] as! String
clinica.latitude = dados?["latitude"] as! String
clinica.longitude = dados?["longitude"] as! String
clinica.urlImagem = dados?["urlImagem"] as! String
clinica.idImagem = dados?["idImagem"] as! String
self.clinicasR.append(clinica)
}
// add annotations to the map
for oneObject in self.todasAnotacoes {
print("Oneobj \(oneObject)")
let umaAnotacao = MinhaAnotacao()
var oneObjLoc: CLLocationCoordinate2D = CLLocationCoordinate2DMake(oneObject.objLat, oneObject.objLong)
umaAnotacao.coordinate = oneObjLoc
umaAnotacao.title = oneObject.objName
umaAnotacao.subtitle = oneObject.objDesc
umaAnotacao.category = oneObject.objCat
self.anotacaoArray.append(umaAnotacao)
self.mapa.addAnnotations(self.anotacaoArray)
}
}
My viewDidLoad is "structured" in 4 main blocks (even though they run in a different order given they are asynchronous):
Definition;
Retrieve user profile;
Retrieve companies information (name, latitude, longitude);
Add annotations to the map.
As those are asynchronous functions, they run in different order and this is what is causing trouble to me.
note: There is still one piece missing here that is to feed all annotations to the variable todasAnotacoes which I'll do once I can retrieve data before triggering add annotation "block".
As annotation info comes from the firebase database, I should only have it executed once the clinicas.observe(DataEventType.childAdded) { (snapshot) in is concluded.
As is today, the sequence Xcode runs is:
Definitions
Add annotations;
retrieve data from firebase with companies details
I've tried adding the add annotation block to the closure, adding a dispatchqueue but none of those really worked. I also did a lot os search in stackoverflow but I couldn't find anything that i can use (or I wasn't able to understand).
So, in summary I need to run the add annotations after retrieving all data from Firebase.
Any ideas on how I could do that?
EDIT 1 - Final code with the suggested updates
override func viewDidLoad() {
super.viewDidLoad()
// Retrieving Logger user data and hidding "add" button if applicable
ProgressHUD.show("Carregando...")
let autenticacao = Auth.auth()
let idUsuarioLogado = (autenticacao.currentUser?.uid)!
let database = Database.database().reference()
let usuarios = database.child("usuarios")
var isUserLoaded = false
var isClinicsLoaded = false
usuarios.child(idUsuarioLogado).observe(DataEventType.value) { (snapshot) in
let dados = snapshot.value as? NSDictionary
let emailUsuario = dados?["email"] as! String
let nomeUsuario = dados?["nome"] as! String
let perfilUsuario = dados?["perfil"] as! String
let idUsuario = snapshot.key
let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
isUserLoaded = true
if (isUserLoaded && isClinicsLoaded) {
self.addAnnotationsToMap();
}
//print("\(usuario.email) e \(usuario.nome) e \(usuario.uid) e \(usuario.perfil)")
}
// Option to code above
/*if Auth.auth().currentUser != nil {
if let uid = (Auth.auth().currentUser?.uid) {
let database = Database.database().reference()
let usuarios = database.child("usuarios").child(uid)
usuarios.observe(.value) { (snapshot) in
let dados = snapshot.value as? NSDictionary
let emailUsuario = dados?["email"] as! String
let nomeUsuario = dados?["nome"] as! String
let perfilUsuario = dados?["perfil"] as! String
let idUsuario = snapshot.key
let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
print("Got here \(usuario.email) e \(usuario.nome) e \(usuario.uid) e \(usuario.perfil)")
if perfilUsuario != "admin" {
self.navigationItem.rightBarButtonItems?.remove(at: 1)
print("Disable + Button")
}
}
}
}*/
// Map - User location
self.mapa.delegate = self
self.gerenciadorLocalizacao.delegate = self
self.gerenciadorLocalizacao.desiredAccuracy = kCLLocationAccuracyBest
self.gerenciadorLocalizacao.requestWhenInUseAuthorization()
self.gerenciadorLocalizacao.startUpdatingLocation()
let clinicas = database.child("clinicas")
// Clinicas listeners
clinicas.observe(DataEventType.value) { (snapshots) in
for child in snapshots.children {
let snapshot = child as! DataSnapshot
print("Clinicas Mapeadas - end")
let dados = snapshot.value as? NSDictionary
//print("Dados na leitura \(dados)")
let clinica = Clinica()
clinica.identificador = snapshot.key
clinica.nome = dados?["nome"] as! String
clinica.endereco = dados?["endereco"] as! String
clinica.cidade = dados?["cidade"] as! String
clinica.cep = dados?["cep"] as! String
clinica.estado = dados?["estado"] as! String
clinica.latitude = dados?["latitude"] as! String
clinica.longitude = dados?["longitude"] as! String
clinica.urlImagem = dados?["urlImagem"] as! String
clinica.idImagem = dados?["idImagem"] as! String
self.clinicasR.append(clinica)
self.todasAnotacoes.append((objLat: Double(clinica.latitude) as! CLLocationDegrees, objLong: Double(clinica.longitude) as! CLLocationDegrees, objName: clinica.nome, objDesc: clinica.endereco, objId: clinica.identificador))
}
isClinicsLoaded = true
if (isUserLoaded && isClinicsLoaded) {
self.addAnnotationsToMap();
}
}
/* NOT IN USE FOR NOW
let latitude = Double(-23.623558)
let longitude = Double(-46.580787)
let localizacao: CLLocationCoordinate2D = CLLocationCoordinate2D.init(latitude: latitude, longitude: longitude)
let span: MKCoordinateSpan = MKCoordinateSpan.init(latitudeDelta: 0.01, longitudeDelta: 0.01)
let regiao = MKCoordinateRegion.init(center: localizacao, span: span)
self.mapa.setRegion(regiao, animated: true)*/
ProgressHUD.dismiss()
}
// add annotations to the map
func addAnnotationsToMap() {
anotacaoArray = []
for oneObject in self.todasAnotacoes {
for oneObject in self.todasAnotacoes {
// print("Oneobj \(oneObject)")
let umaAnotacao = MinhaAnotacao()
var oneObjLoc: CLLocationCoordinate2D = CLLocationCoordinate2DMake(oneObject.objLat, oneObject.objLong)
umaAnotacao.coordinate = oneObjLoc
umaAnotacao.title = oneObject.objName
umaAnotacao.subtitle = oneObject.objDesc
umaAnotacao.identicadorMapa = oneObject.objId
self.anotacaoArray.append(umaAnotacao)
print("Annotation added \(todasAnotacoes.count) - end")
}
self.mapa.addAnnotations(self.anotacaoArray)
self.todasAnotacoes = []
self.anotacaoArray = []
// print("Annotations added 2 - end")
}
}
The second observer in your code is using .childAdded, which means that closure gets called for each individual child node under clinicas. This makes it hard to know when you're done with clinics, so I recommend first switching that observer over to .value, with something like:
clinicas.observe(DataEventType.value) { (snapshots) in
for child in snapshots.children {
let snapshot = child as! DataSnapshot
let dados = snapshot.value as? NSDictionary
//print("Dados na leitura \(dados)")
let clinica = Clinica()
clinica.identificador = snapshot.key
clinica.nome = dados?["nome"] as! String
clinica.endereco = dados?["endereco"] as! String
clinica.cidade = dados?["cidade"] as! String
clinica.cep = dados?["cep"] as! String
clinica.estado = dados?["estado"] as! String
clinica.latitude = dados?["latitude"] as! String
clinica.longitude = dados?["longitude"] as! String
clinica.urlImagem = dados?["urlImagem"] as! String
clinica.idImagem = dados?["idImagem"] as! String
self.clinicasR.append(clinica)
}
// At this point we're done with all clinics
}
As you'll have notice this code now uses a loop to iterate over snapshots.children. So we're processing all child nodes of clinicas in one closure, which makes it much easier to know when we're done.
Now with the above change we have two closures that get called individually, and you have some code that you want to run when both of them are done. There are a few ways to synchronize this code.
The first way is what you've already tried: putting the clinicas.observe block into the closure of usuarios.child(idUsuarioLogado).observe(. Now that the clinics observer uses .value, you'll find that this is much easier to get working.
But I'd like to show an alternative below, where we use two simple flags to determine if both closures are done. For this you will have to put the code that runs after the data is loaded into a separate function, something like this:
func addAnnotationsToMap() }
for oneObject in self.todasAnotacoes {
...
}
}
Now in each of the two closures, we're going to set a flag when they're done. And then at the end of each of the closures, we'll detect whether both flags are set, and then call the new function if we have all data we need.
So at the start of viewDidLoad we define our two flags:
let isUserLoaded = false
let isClinicsLoaded = false
And then at the end of loading the user:
usuarios.child(idUsuarioLogado).observe(DataEventType.value) { (snapshot) in
let dados = snapshot.value as? NSDictionary
let emailUsuario = dados?["email"] as! String
let nomeUsuario = dados?["nome"] as! String
let perfilUsuario = dados?["perfil"] as! String
let idUsuario = snapshot.key
let usuario = Usuario(email: emailUsuario, nome: nomeUsuario, uid: idUsuario, perfil: perfilUsuario)
isUserLoaded = true
if (isUserLoaded && isClinicsLoaded) {
addAnnotationsToMap();
}
}
And similar code (setting the isClinicsLoaded flag) to the other closure.
With these in place, you'll start loading the user data and clinics when the code runs, and then whichever one of them completes last, will call the new addAnnotationsToMap function.
Another alternative is to use a DispatchGroup, which is an Apple-specific construct that can also make this much simpler. Read more about it in this answer (and the links from there): Wait until swift for loop with asynchronous network requests finishes executing

Get Distance between two points Swift 5

I'm trying to get the distance between tow points, current location, and another point.
All the data is calculate great, but I can't get the value to asign it to the object, and then create the table.
The distance is created later than the object even I hace make an async queue.
I can't find the problem maybe I'm too new in this.
Thanks for the help
DispatchQueue.global(qos: .utility).async {
for snap in snapshot.children {
let postSnap = snap as! DataSnapshot
if let dict = postSnap.value as? [String:AnyObject] {
let farcoordenadas = dict["coordenadas"] as! [Double]
let lat = (farcoordenadas[0] as! Double)
let long = (farcoordenadas[1] as! Double)
let location = CLLocationCoordinate2D(latitude: lat, longitude: long)
let farcalle = (dict["calle"] as! String)
let fartelefono = (dict["telefono"] as! String)
let farpoblacion = (dict["poblacion"] as! String)
let farhorario = (dict["horario"] as! String)
let request = MKDirections.Request()
request.source = MKMapItem(placemark: MKPlacemark(coordinate:self.userLocation!))
request.destination = MKMapItem(placemark: MKPlacemark(coordinate: location))
request.transportType = .automobile
let directions = MKDirections(request: request)
directions.calculate { (response, error) -> Void in
if let response = response, let route = response.routes.first {
print(route.distance)
//converts from meters to miles
var routeDistance = route.distance/1000
//formats the string to two decimals.
distancia = String(format: "%.0f" , routeDistance)
}
return
}
DispatchQueue.main.async {
let farmaciapueblo = Farmacia(fecha:dateString, horario:farhorario, poblacion: farpoblacion, telefono:fartelefono, calle:farcalle, coordenadas:farcoordenadas, distancia:distancia)
self.farmaciasrural.append(farmaciapueblo)
self.farmaciasrural.sort(by: myLocation) // mutating version
self.activityIndicator.stopAnimating()
self.tableView.reloadData()
}
}
}
}

Save geoFire location under random firebase id swift 4

I'm trying to save the geofire information under each postId but currently not quite sure how to do so. Could someone please help me figure out how to save the information under the postId node.
#objc func handlePost() {
navigationItem.rightBarButtonItem?.isEnabled = false
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let caption = textView.text, caption.characters.count > 0 else { return }
let userPostRef = Database.database().reference().child("posts").child(uid)
let ref = userPostRef.childByAutoId()
guard let locationName = locationNameButton.titleLabel?.text else { return }
let latitude = lat
let longitude = long
let geoLatitude = (latitude as! NSString).doubleValue
let geoLongitude = (longitude as! NSString).doubleValue
geoFireRef = Database.database().reference().child("posts").child(uid)
geoFire = GeoFire(firebaseRef: geoFireRef)
let values = ["caption": caption,"locationName": locationName, "latitude": latitude,"longitude": longitude,"creationDate": Date().timeIntervalSince1970] as [String : Any]
geoFire?.setLocation(CLLocation(latitude: geoLatitude, longitude: geoLongitude), forKey: ref.key!)
If you want to store the geolocation under the same key as
let ref = userPostRef.childByAutoId()
You can simply get the key property from ref. So:
geoFire?.setLocation(CLLocation(latitude: geoLatitude, longitude: geoLongitude), forKey: ref.key)

How to access a dictionary value with Swift 3?

So since the release of Swift 3, a part of my code where I access a dictionary isn't working anymore, here is the code with the previous release of swift:
var locationDict: NSDictionary?//location dictionary
if let getLocation = item.value?["Location"]{locationDict = getLocation as? NSDictionary}
//get dictionary values
let getLatitude = locationDict?.valueForKey("latitude") as! Double
let getLongitude = locationDict?.valueForKey("longitude") as! Double
Now with the new release I'm not sure how to rewrite "getLocation". I only rewrote the last two lines with the new syntax:
//get dictionary values
let getLatitude = locationDict?.value(forKey: "latitude") as! Double
let getLongitude = locationDict?.value(forKey: "longitude") as!
I am using Firebase, this is the complete function: (it adds an array of annotations to a map)
func setAnnotations(){
//get data
ref.child("Stores").observe(.value, with: { (snapshot) in
self.mapView.removeAnnotations(self.annArray)
for item in snapshot.children {
let annotation = CustomAnnotation()
//set all data on the annotation
annotation.subtitle = (snapshot.value as? NSDictionary)? ["Category"] as? String
annotation.title = (snapshot.value as? NSDictionary)? ["Name"] as? String
annotation.annImg = (snapshot.value as? NSDictionary)? ["Logo"] as? String
var locationDict: NSDictionary?//location dictionary
if let getLocation = item.value?["Location"]{locationDict = getLocation as? NSDictionary}
let getLatitude = locationDict?.value(forKey: "latitude") as! Double
let getLongitude = locationDict?.value(forKey: "longitude") as! Double
annotation.coordinate = CLLocationCoordinate2D(latitude: getLatitude, longitude: getLongitude)
self.annArray.append(annotation)
self.mapView.addAnnotation(annotation)
}
})
}
Try this:-
func setAnnotations(){
//get data
FIRDatabase.database().reference().child("Stores").observe(.value, with: { (snapshot) in
self.mapView.removeAnnotations(self.annArray)
for item in snapshot.children{
if let itemDict = (item as! FIRDataSnapshot).value as? [String:AnyObject]{
annotation.subtitle = itemDict["Category"] as! String
annotation.title = itemDict["Name"] as! String
annotation.annImg = itemDict["Logo"] as! String
if let locationDict = itemDict["Location"] as? [String:AnyObject]{
let getLatitude = locationDict["latitude"] as! Double
let getLongitude = locationDict["longitude"] as! Double
annotation.coordinate = CLLocationCoordinate2D(latitude: getLatitude, longitude: getLongitude)
self.annArray.append(annotation)
self.mapView.addAnnotation(annotation)
}
}
}
})
}
Things get substantially easier if you cast to a type-safe dictionary, e.g.:
snapshot.value! as! [String:Any]
For a slightly larger example, see the code from this answer I wrote earlier today:
ref!.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let msg = child as! FIRDataSnapshot
print("\(msg.key): \(msg.value!)")
let val = msg.value! as! [String:Any]
print("\(val["name"]!): \(val["message"]!)")
}
})

iOS Core Data: Convert result of fetch request to an array

I'm trying to put the results of a fetch request into an array. My code:
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "CLIENTS")
var mobClients = [NSManagedObject]()
var arrayAllPhoneNumbers = [String]()
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
mobClients = results as! [NSManagedObject]
for clientPhoneNumber in mobClients {
let myClientPhoneNumber = clientPhoneNumber.valueForKey("clientsMobilePhoneNumber") as! String
print(myClientPhoneNumber)
//The numbers print out just fine, one below the other
//
//Now the results need to go into the array I've declared above ---> arrayAllPhoneNumbers
messageVC.recipients = arrayAllPhoneNumbers // Optionally add some tel numbers
}
} catch
let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
As illustrated, all the phone numbers needs to be captured in an array. How do I accomplish that?
Instead of your for-loop and the code inside it, use this:
arrayAllPhoneNumbers = mobClients.map({ clientPhoneNumber in
clientPhoneNumber.valueForKey("clientsMobilePhoneNumber") as! String
})
messageVC.recipients = arrayAllPhoneNumbers
let request = NSFetchRequest(entityName: "CLIENTS")
let results = (try? managedContext.executeFetchRequest(request)) as? [NSManagedObject] ?? []
let numbers = results.flatMap { $0.valueForKey("clientsMobilePhoneNumber" as? String }
numbers is now an array of your phone numbers.
But like thefredelement said, it's better to subclass it so you can just cast it to that subclass and access the phone numbers directly.
Swift 5 : flatMap is deprecated, use compactMap
let request = NSFetchRequest(entityName: "CLIENTS")
let results = (try? managedContext.executeFetchRequest(request)) as? [NSManagedObject] ?? []
let numbers = compactMap { $0.valueForKey("clientsMobilePhoneNumber" as? String }