How to access a dictionary value with Swift 3? - swift

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"]!)")
}
})

Related

Not able to read data from Firebase realtime database

I have stored the comments under a post in firebase realtime database. The problem i have is that when i try to parse out the data from firebase i get an error that says Unexpectedly found nil while unwrapping an Optional value. So for example if i try to pront the data stored under degree, i get this nil error. But when i print "comments" instead of the "degree" i successfully fetch the data. My database structure looks like this.
func obeserveComments() {
// get auto-id of post
let commentKey = self.keyFound
let postRef = Database.database().reference().child("posts").child(commentKey)
var tempComments = [Comments]()
postRef.observe(.value, with: {(snapshot) in
if let dict = snapshot.value as? [String:Any] {
if let comments = dict["comments"] as? [String:Any] {
let degree = comments["reply degree"] as! String
// let name = comments["reply name"] as! String
// let text = comments["reply text"] as! String
// let university = comments["reply university"] as! String
// let photoURL = comments["reply url"] as! String
// let url = URL(string: photoURL)
// let timestamp = comments["timestamp"] as! Double
print(degree)
}
}
})
}
The answer by #aytroncb is a good answer, I prefer to leave Firebase data 'Firebasy' as long as possible. In other words coverting to dictionaries looses ordering and and find code like this
[String: [String: [String: Any]]]
To be very hard to read.
I prefer
let snap = snapshot.childSnapshot("comments") //snap becomes a DataSnapshot
So my solution maintains the order and leverages .childSnapshot to leave data in it's DataSnapshot form.
func readPostComments() {
let postRef = self.ref.child("posts") //self.ref points to my firebase
postRef.observeSingleEvent(of: .value, with: { snapshot in
let allPosts = snapshot.children.allObjects as! [DataSnapshot]
for postSnap in allPosts {
print("postId: \(postSnap.key)")
let commentsSnap = postSnap.childSnapshot(forPath: "comments") //will be a DataSnapshot
let allComments = commentsSnap.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
print(" commentId: \(commentSnap.key)")
let replyDegree = commentSnap.childSnapshot(forPath: "reply_degree").value as? String ?? "No Degree"
let replyName = commentSnap.childSnapshot(forPath: "reply_name").value as? String ?? "No Name"
print(" degree: \(replyDegree) by: \(replyName)")
}
}
})
}
EDIT
For a single post, remove the top part of the code that reads in and iterates over all posts.
func readCommentsForOnePost() {
let postRef = self.ref.child("posts")
let postCommentRef = postRef.child("post_0")
postCommentRef.observeSingleEvent(of: .value, with: { snapshot in
print("postId: \(snapshot.key)")
let commentsSnap = snapshot.childSnapshot(forPath: "comments") //will be a DataSnapshot
let allComments = commentsSnap.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
print(" commentId: \(commentSnap.key)")
let replyDegree = commentSnap.childSnapshot(forPath: "reply_degree").value as? String ?? "No Degree"
let replyName = commentSnap.childSnapshot(forPath: "reply_name").value as? String ?? "No Name"
print(" degree: \(replyDegree) by: \(replyName)")
}
})
}
Its because firebase is returning your data like this
{
"MAKFW244kdL)Cw;1": [Array of data],
"LOPSw!35pa3flAL4": [Array of data],
"ALV34VR4_A6Vn1a": [Array of data]
}
So change your initial casting of snapshot.value to this:
if let dict = snapshot.value as? [String: [String: Any]]
then loop through that new dictionary like this:
for objectJson in dict.values {
if let comments = objectJson["comments"] as? [String: [String: Any]] {
for commentJson in comments.values {
let degree = commentJson["reply_degree"] as? String
}
}
}
Update
Just read through your post again and noticed your trying to access the comments directly with a key, your first going to need to provide the PostId. Then you can use the above code to loop through the objects
let postRef = Database.database().reference().child("posts").child(postID)
alternatively I believe you can have the comments returned as a normal list by doing something like this:
let postRef = Database.database().reference().child("posts").child("\(postID)/{id}")

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

swift snapshot firebase; which child it came from

I need a pretty simple thing and I cant figure it out. I created a snapshot of firebase, and i matched the userID with the snapshots name of inside a child. I just need the childs ID (which i created using childbyautoID)
here is my code:
func checkIfUserIsLoggedIn(){
if Auth.auth().currentUser?.uid == nil {
} else {
let uid = Auth.auth().currentUser?.uid
Database.database().reference().child("Users").child(uid!).child("data").observeSingleEvent(of: .value, with: {(snapshot) in
if let dictionary = snapshot.value as? [String:AnyObject] {
self.fnamefinal = dictionary["email"] as? String
if self.fnamefinal != nil {
self.ref.child("Request").observe(.childAdded, with: { (snapshot) in
let results = snapshot.value as? [String : AnyObject]
let name = results?["name"]
let garage = results?["garage"]
let time = results?["time"]
let latitude = results?["latitude"]
let longitude = results?["longitude"]
let childID = results?[""]
print(snapshot)
print(childID as! String?)
if name as! String? == self.fnamefinal {
let myCalls = RidesRequestedd(name: name as! String?, garage: garage as! String?, time: time as! String?, latitude: latitude as! Double?, longitude: longitude as! Double?)
self.frequests1.append(myCalls)
self.rideslabel.text = myCalls.garage
} else {
}
})
} else {
print("no")
}
}
})
//}
}
Here is the snapshot of the matched name with user ID:
Snap (-LMBAF69-kYKnWoK2n9M) {
garage = "Coliseum/Bobcat Stadium";
latitude = "29.89";
longitude = "-97.953";
name = "test3#gmail.com";
time = "12:13 AM";
}
I just need the LMBAF69.... string. Simple but i cant figure it out

Representing Coordinates from Firebase as Annotations Swift

I am having trouble putting annotations on my map from longitudinal and latitudinal degrees I uploaded to firebase. I want to add an annotation for all of my users but cannot get xcode to recognize my userLatitude and userLongitude variables in another function.
Anything will help!
func retrieveUsers(){
let ref = Database.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
let users = snapshot.value as! [String: AnyObject]
self.user.removeAll()
for (_,value) in users {
if let uid = value["uid"] as? String {
if uid != Auth.auth().currentUser!.uid {
let userToShow = User()
if let fullName = value["full name"] as? String,
let imagePath = value["urlToImage"] as? String,
//String(format: "%f", self.currentUserLocation?.longitude ?? 0),
let userLongitude = value["long"] as? CLLocationDegrees,
let userLatitude = value["lat"] as? CLLocationDegrees
//why isnt it recognizing the users degrees
{
userToShow.fullName = value["full name"] as? String
userToShow.imagePath = value["urlToImage"] as? String
userToShow.userID = value["uid"] as? String
userToShow.userLongitude = value["long"] as? CLLocationDegrees
userToShow.userLatitude = value["lat"] as? CLLocationDegrees
self.user.append(userToShow)
}
}
}
}
DispatchQueue.main.async {
self.map.reloadInputViews()
//not sure if this is right
}
})
}
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
let otherUserLocation: CLLocationCoordinate2D = CLLocationCoordinate2DMake(userLatitude, userLongitude)
let userAnnotation = MKPointAnnotation()
userAnnotation.coordinate = otherUserLocation
userAnnotation.title = "fullName"
}
And if you change as? CLLocationDegrees by as? String and manage the cast in CLLocationDegrees afterwards?
Because I think, in Firebase, data is stored as String and not as Double (that is the type of data a CLLocationDegrees type is referring to).

reading data on firebase

This is my data structure.
This is how I load club data and its address.
func loadClubs() {
ref = Database.database().reference()
ref.child("club").observe(DataEventType.childAdded, with: { (clubSnapshot) in
if let clubDict = clubSnapshot.value as? [String : AnyObject] {
let name = clubDict["name"] as! String
let explanation = clubDict["explanation"] as! String
let courtNum = clubDict["courtNum"] as! Int
let membershipFee = clubDict["membershipFee"] as! Int
let visitorFee = clubDict["visitorFee"] as! Int
let hasCarParking = clubDict["hasCarParking"] as! Bool
let club2 = Club2(name: name, explanation: explanation, courtNum: courtNum, membershipFee: membershipFee, visitorFee: visitorFee, hasCarParking: hasCarParking)
self.club2Array.append(club2) // Add to clubArray
print(self.club2Array)
self.tableView.reloadData()
}
let addressRef = Database.database().reference()
addressRef.child("address").child(clubSnapshot.key).observe(DataEventType.childAdded, with: { (addressSnapshot) in
if let addressDict = addressSnapshot.value as? [String: AnyObject] {
let clubAddress = ClubAddress(postCode: addressDict["postcode"] as! String, cityName: addressDict["city"] as! String, ward: addressDict["ward"] as! String, address1: addressDict["address1"] as! String, address2: addressDict["address2"] as! String)
self.addressArray.append(clubAddress)
print(self.addressArray)
}
})
})
}
basically, after retrieving each snapshot of club, I get club's key (-KsJB9TkoGNIkiZFGg7), then use that key to retrieve address.
However, print(self.addressArray) doesn't not print anything.
I add a debug breakpoint at if let addressDict = addressSnapshot.value as? [String: AnyObject] { , it does not stop the debug process.
Why is it not calling?
What do I miss here?
Ah! Your code is very close.
Keep in mind that .childAdded iterates over all of the child nodes and loads each one.
In your case, you don't want to iterate over all of the address nodes, you just want one, and don't want to leave an observer.
To do that, we load the specific node child data of the address node, by observeSingleEvent(of: .value. Here's a snippet of the important part.
let addressRef = Database.database().reference()
addressRef.child("address").child(clubSnapshot.key)
.observeSingleEvent(of: .value, with: { (addressSnapshot) in
let dict = addressSnapshot.value as! [String: Any]
let address = dict["address1"] as! String
print(address)