I have a collection view where you scroll vertically. Inside each collection view cell I have a tableview which consists of comments. I load data from Firebase to both the collection view and tableview.
What I'm trying to do is to load specific data and the correct number of rows In the tableview inside the collection cell based on the photoID from a photo in my collection cell. Basically I want the right comments and the right numberofrows to each tableview inside each collectionviewcell
The problem is that the comments doesn't appear in the right cells and also the numberofrows in the tableviews In the different collection view cells are not correct.
in my collectionviewcontroller I use this Func to get my data.
func fetchData(){
let ref = Database.database().reference()
ref.child("photos").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let photo = Photo(dictionary: dictionary)
photo.videoId = snapshot.key
self.photos.append(photo)
if let photoId = photo.videoId{
self.photoTimeStampDictionary[photoId] = photo
}
DispatchQueue.main.async {
self.cView.reloadData()
}
self.attemptReloadCollectionView()
}
}, withCancel: nil)
}
then in cellforitem.
let photo = photos[indexPath.row]
cell.photo = photo
return cell
// I then use the photo variable inside my collectionviewcell.
This is my database structure:
**Comments**
**randomID**
[commenttext: "blabla"]
[uid: the senders UID]
[photo ID: the photoID]
**Photos**
**photo ID**
[and some more info here ofc]
My model class.
class Comment: NSObject {
var commentId: String?
var commentText: String?
var photoId: String?
var timeStamp: NSNumber?
var uid: String?
init(dictionary: [String: AnyObject]) {
self.commentId = dictionary["commentid"] as? String
self.commentText = dictionary["commenttext"] as? String
self.photoId = dictionary["photoid"] as? String
self.timeStamp = dictionary["timestamp"] as? NSNumber
self.uid = dictionary["uid"] as? String
}
}
The collection view works fine and just as it should.
This is what I have tried. (This is inside the collectionviewcell)
var comments = [Comment]()
func fetchData(){
let ref = Database.database().reference()
ref.child("comments").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let comment = Comment(dictionary: dictionary)
// comment.commentId = snapshot.key
self.comments.append(comment)
DispatchQueue.main.async {
self.commentsTableView.reloadData()
}
}
}, withCancel: nil)
}
and then in cellforrow:
let comment = comments[indexPath.row]
if comment.photoId == self.photo?.videoId{
cell.commentText.text = comment.commentText
}
photo is my model class for all info connected to setting up the collection view cell except for the tableview. With the above code one specific comment that belongs to one specific picture ends up showing at several photos sometimes.
I would be grateful for help with this problem. I can imagine it has to do something with how you reload the tableview and also detect the index path in the collection view.
Thanks in advance.
Best regards,
Wurzel
Try removing the asynchronous func :
var comments = Comment
func fetchData(){
let ref = Database.database().reference()
ref.child("comments").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
let comment = Comment(dictionary: dictionary)
// comment.commentId = snapshot.key
self.comments.append(comment)
self.commentsTableView.reloadData()
}
}, withCancel: nil)
}
If that's indeed the issue, I will elaborate further.
Related
I have an app where when a user taps a cell from a TableView representing a group of images, he is taken to another tableView where all the images within that group should be shown. However I am unsure of how to do this.
I have currently have extracted at the beginning the info necessary to make a reference and get all the data, which is in an array of objects. However how can I access these values from another class?
DataModel:
struct UserImage {
var userID: String
var image: UIImage
var postNum: String
}
I am creating an array of this as shown bellow, in a P1TableVC:
let arrayOfUserImageData = [UserImage]()
The function which retrieves and store the data looks as follows:
func fetchAllUserFristImage() {
print("Description: calling of fetchAllUserFristImage()")
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if snapshot.value as? [String: AnyObject] != nil {
let user = snapshot.key
print("Description: calling of snapshot.value is not nil ")
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? [String:AnyObject] {
for (p) in postDictionary2 {
if let posts = p.value as? [String:AnyObject] {
print("Description: posts has value of: \(posts)")
//to get back to where i was delete the below for i
for (i) in posts {
if let imageUrlString = i.value as? [String:AnyObject], let postUrl = imageUrlString["image1"] as? String {
print("Description: inside the if let imageUrlString = i.value ")
self.feedArray.append(Post(fetchedImageURL: postUrl))
if let imageUrl = URL(string: postUrl), let imageDataL = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageDataL) {
print("Description: inside the if let imageUrl = URL(string: postUrl)")
print("Description: img url's of posts: \(imageUrl)")
self.tableData.append(UserImage(userID: user, image: image, postNum: p.key))
self.tableView.reloadData()
} else {print("this user had no posts, was nil")}
}
}
}
}
}
})
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
In didSelectItem method fetch the selected data from tableData based on indexPath.row and pass this data to next controller (make a variable in next controller and before pushing/presenting assign the fetched data to that variable).
didSelectItem : Fetch the selected data - let data = tableData[indexPath.row]
Assign it to next controller's variable -
let vc = nextVC()
vc.fetchedData = data
push/present vc
Or you may probably use Singleton
For instance: just create class Model
class Model {
static let sharedInstance = Model()
var tableVC: P1TableVC!
}
Then inside of your VC in viewDidLoad
class P1TableVC: UIViewController {
override func viewDidLoad() {
Model.sharedInstance.tableVC = self
}
}
Then you can use your class everywhere you want
Model.sharedInstance.tableVC.arrayOfUserImageData
Or you can create variable for all your data
class Model {
static let sharedInstance = Model()
var data: [UserImage]()
}
And then use data not VC
Model.sharedInstance.data
In order to populate my tableView, I append items (created from a struct) to a local array:
func loadList() {
var newAnnotations: [AnnotationListItem] = []
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").queryOrderedByKey().observeSingleEvent(of: .value, with: {snapshot in
for item in snapshot.children {
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
annotationList = newAnnotations
self.tableView.reloadSections([0], with: .fade)
})
}
}
When I click a specific row, I am taken to a DetailViewController where it is only a large UITextView (named notes). The UITextView.text displayed is based on the selected indexPath.row and the "notes" value is retrieved from the array. Now the user is able to type some text and when they are done, the textViewDidEndEditing function is called:
func textViewDidEndEditing(_ textView: UITextView) {
notes.resignFirstResponder()
navigationItem.rightBarButtonItem = nil
let newNotes = self.notes.text
print(newNotes!)
}
Now I'd like to updateChildValues to newNotes to the child node "notes" in my JSON:
"users" : {
"gI5dKGOX7NZ5UBqeTdtu30Ze9wG3" : {
"annotations" : {
"-KuWIRBARv7osWr3XDZz" : {
"annotationSubtitle" : "1 Cupertino CA",
"annotationTitle" : "Apple Infinite Loop",
"notes" : "Does it work?!",
}
How can I access the selected autoID so I can update the specific notes node. So far the best I have is:
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(somehow access the specific childID).updateChildValues(["notes": newNotes])
Any help will be greatly appreciated. Thanks in advance
UPDATE
The annotationListItem struct is created:
struct AnnotationListItem {
let key: String?
var annotationTitle: String?
let annotationSubtitle: String?
let notes: String?
let ref: DatabaseReference?
init(key: String = "", annotationTitle: String, annotationSubtitle: String, notes: String) {
self.key = key
self.annotationTitle = annotationTitle
self.annotationSubtitle = annotationSubtitle
self.notes = notes
self.ref = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
annotationTitle = snapshotValue["annotationTitle"] as? String
annotationSubtitle = snapshotValue["annotationSubtitle"] as? String
notes = snapshotValue["notes"] as? String
ref = snapshot.ref
}
init(Dictionary: [String: AnyObject]) {
self.key = Dictionary["key"] as? String
self.annotationTitle = Dictionary["annotationTitle"] as? String
self.annotationSubtitle = Dictionary["annotationSubtitle"] as? String
self.notes = Dictionary["notes"] as? String
self.ref = nil
}
func toAnyObject() -> Any {
return [
"annotationTitle": annotationTitle as Any,
"annotationSubtitle": annotationSubtitle as Any,
"notes": notes as Any
]
}
}
UPDATE
This is how the annotationListItem is created to be stored in Firebase:
// Using the current user’s data, create a new AnnotationListItem that is not completed by default
let uid = Auth.auth().currentUser?.uid
guard let email = Auth.auth().currentUser?.email else { return }
let title = placemark.name
let subtitle = annotation.subtitle
let notes = ""
// declare variables
let annotationListItem = AnnotationListItem(
annotationTitle: title!,
annotationSubtitle: subtitle!,
notes: notes)
// Add the annotation under their UID
let userAnnotationItemRef = uidRef.child(uid!).child("annotations").childByAutoId()
userAnnotationItemRef.setValue(annotationListItem.toAnyObject())
I think you only need to do this:(since you have declared the note as global)
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(note.key).updateChildValues(["notes": newNotes])
inside the method where you change the notes
If I am not mistaken you are creating an array of a custom object?
var newAnnotations: [AnnotationListItem] = []
You could do something like: var newAnnotations: [(key: String, value: [String : Any])] = [] (Any only if you are going to have Strings, Integers, ect. If it'll only be String then specify it as a String.
Accessing the key would be: newAnnotations[indexPath.row].key in your cellForRowAtIndex of your tableView. Accessing values would be: newAnnotations[indexPath.row].value["NAME"].
You can have a separate array that holds the key and just append it at the same time as your population:
for item in snapshot.children {
guard let itemSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key //This is the ID
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
Another thing you could do is go up one more level in your firebase call:
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: {snapshot in
if snapshot is NSNull{
//Handles error
} else{
if let value = snapshot.value as? NSDictionary{ //(or [String: String]
//set localDictionary equal to value
}
}
self.tableView.reloadSections([0], with: .fade)
})
}
And then when you select a row: let selectedItem = localDictionary.allKeys[indexPath.row] as! String //This is the ID you pass to your viewController.
I've spend hours looking at identical questions but none of the answers I've found are helping this issue. Simple app retrieves data from Firebase Database and passes to another view controller from the tableview. The main data will pass through but I can't edit the information without an identifying "key" which I tried to set as childByAutoID() but then changed to a timestamp. Regardless of the method, all I get is the entries info not the actual key itself.
func loadData() {
self.itemList.removeAll()
let ref = FIRDatabase.database().reference()
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
if let todoDict = snapshot.value as? [String:AnyObject] {
for (_,todoElement) in todoDict {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
print (snapshot.key);
}
}
self.tableView.reloadData()
}) { (error) in
print(error.localizedDescription)
}
}
If your data looks like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”
}
}
}
Then I would query like this:
ref.child(userID!).child("MyStuff").observeSingleEvent(of: .value, with: { (snapshot) in
for child in snapshot.children {
let child = child as? DataSnapshot
let key = child?.key as? String
if let todoElement = child?.value as? [String: Any] {
let todo = TheItems()
todo.itemName = todoElement["itemName"] as? String
todo.itemExpires = todoElement["itemExpires"] as? String
todo.itemType = todoElement["itemType"] as? String
self.itemList.append(todo)
self.tableView.reloadData()
}
}
})
Additionally, like I said in my comment you can just upload the key with the data if you’re using .updateChildValues(). Example:
let key = ref.child("userID!").childByAutoId().key
let feed = ["key": key,
“itemName”: itemName] as [String: Any]
let post = ["\(key)" : feed]
ref.child("userID").child("MyStuff").updateChildValues(post) // might want a completionBlock
Then you can get the key the same way you are getting the rest of the values. So your new data would look like this:
Uid: {
MyStuff: {
AutoID: {
itemName: “Apocalypse”,
itemExpires: “December 21, 2012”,
itemType: “Catastrophic”,
key: “autoID”
}
}
}
The key you are trying to look for is located in the iterator of your for loop
Inside your if-let, try to do this:
for (key,todoElement) in todoDict {
print(key) // this is your childByAutoId key
}
This should solve the problem. Otherwise show us a screen of your database structure
Swift 3 and Firebase - I have successfully managed to fetch users' usernames and email addresses in tableview cells. However, when a user changes his username on a separate view controller, the tableview is not updating - the old username is still shown. Please have a look at the code below:
databaseRef.child("users").queryOrdered(byChild: "username").observe(.childAdded, with: { (snapshot) in
let key = snapshot.key
let snapshot = snapshot.value as? NSDictionary
snapshot?.setValue(key, forKey: "uid")
if(key == self.loggedInUser?.uid)
{
// don't add signed in user to array
}
else
{
var theUser = User()
theUser.key = key
theUser.fullname = snapshot?.value(forKey: "fullname") as? String
theUser.biography = snapshot?.value(forKey: "biography") as? String
theUser.location = snapshot?.value(forKey: "location") as? String
theUser.photoURL = snapshot?.value(forKey: "photourl") as? String
theUser.username = snapshot?.value(forKey: "username") as? String
self.arrayOfUsers.append(theUser)
//insert the rows
self.SearchUsersTableViewController.insertRows(at: [IndexPath(row:self.arrayOfUsers.count-1,section:0)], with: UITableViewRowAnimation.automatic)
self.tableView.reloadData()
}
})
I have tried placing tableview.reloadData() on multiple places without success. I have also tried using:
DispatchQueue.main.async {
self.tableView.reloadData()
}
without success. I came up with an idea to append the user again when he changes his username. However, I didn't know how to delete the old one. I thought the best solution was to add observe of type childChanged. The problem was that I couldn't find the index of the user who changed his username in users array.
I would be grateful if someone helps me solve the problem.
EDIT:
I have a struct User:
struct User {
var username: String?
var photoURL: String?
var biography: String?
var fullname: String?
var location: String?
var key: String?
}
For .childChanged I have used the code that Priyamal suggested:
databaseRef.observe(.childChanged, with: { snapshot in
let ID = snapshot.key //this is the firebaseKey
if let index = self.arrayOfUsers.index(where: {$0.key == ID}) {
let changedPost = self.arrayOfUsers[index]
//update the values
self.tableView.reloadData()
print("Change!")
}
})
However, when I change the username, I never get to the "Change!" output in my console; therefore, the tableview is not changed.
i think you need to change the event type from childAdded to childChanged
with child changed you will only get the updated value. then you must update the existing element in your array.
let's assume your User Struct looks like this
struct User {
var keyID : String?
var name : String?
}
var userArray = [User]() //this represents the array holding user objects
this method will get called if an update happens
databaseRef.observeEventType(.ChildChanged, withBlock: { snapshot in
let ID = snapshot.key //this is the firebaseKey
if let index = self. userArray.indexOf({$0.keyID == ID}) {
let changedPost = self. userArray[index]
//update the values
self.tableView.reloadData
}
to load UserArray at the first place use this method.
databaseRef.child("users").queryOrdered(byChild: "username").observe(.value, with: { (snapshot) in
let key = snapshot.key
let snapshot = snapshot.value as? NSDictionary
snapshot?.setValue(key, forKey: "uid")
if(key == self.loggedInUser?.uid)
{
print("Should not be shown!")
}
else
{
self.usersArray.append(snapshot)
self.SearchUsersTableViewController.insertRows(at: [IndexPath(row:self.usersArray.count-1,section:0)], with: UITableViewRowAnimation.automatic)
self.tableView.reloadData()
}
})
I have successfully solved the problem. After I got the index, I only needed to update the user at that specific location in the array and refresh that particular row in the table view!
databaseRef.child("users").queryOrdered(byChild: "username").observe(.childChanged, with: { (snapshot) in
let ID = snapshot.key
if let index = self.arrayOfUsers.index(where: {$0.key == ID}) {
let value = snapshot.value as? NSDictionary
self.arrayOfUsers[index].username = value?["username"] as? String
let indexPath = IndexPath(item: index, section: 0)
self.tableView.reloadRows(at: [indexPath], with: .top)
}
})
I am trying to print array from the firebase. Actually if we tap a medication in a list(tableviewcontroller), it will show its specfic dosages. I got stucked to retrieve the dosages list. Here is my code to get data from firebase. Any help is appreciated. Thanks in advance. My firebase structure looks like this.. firebase img
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observeEventType(.Value, withBlock: { snapshot in
for item in snapshot.children{
FIRDatabase.database().reference().child("medication").child("options").observeEventType(.Value, withBlock: {snapshot in
print(snapshot.value)
})
}
})
You should take a look on firebase documentation https://firebase.google.com/docs/database/ios/read-and-write
but if I'm understanding your idea, you probably has a model class for your medications. So, to retrieve your data you should do like this for Swift 3.0:
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observe(.value, with: { (snapshot) in
for item in snapshot.children{
// here you have the objects that contains your medications
let value = item.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let dossage = value?["dossage"] as? String ?? ""
let type = value?["type"] as? String ?? ""
let options = value?["options"] as? [String] ?? ""
let medication = Medication(name: name, dossage: dossage, type: type, options: options)
// now you populate your medications array
yourArrayOfMedications.append(medication)
}
yourTableView.reloadData()
})
}
Now that you have your array with all your medications, you just need to populate your tableView with this medications. When someone press an item on table you can just call prepareForSegue: and send your yourArrayOfMedications[indexPath.row].options to the next view
The solution is same as above but with a small change.
func loadDataFromFirebase() {
databaseRef = FIRDatabase.database().reference().child("medication")
databaseRef.observe(.value, with: { (snapshot) in
for item in snapshot.children{
// here you have the objects that contains your medications
let value = item.value as? NSDictionary
let name = value?["name"] as? String ?? ""
let dossage = value?["dossage"] as? String ?? ""
let type = value?["type"] as? String ?? ""
let options = value?["options"] as? [String : String] ?? [:]
print(options["first"]) // -> this will print 100 as per your image
// Similarly you can add do whatever you want with this data
let medication = Medication(name: name, dossage: dossage, type: type, options: options)
// now you populate your medications array
yourArrayOfMedications.append(medication)
}
yourTableView.reloadData()
})
}