Updating tableview without reloading the view (Swift, Firebase) - ios

So I am trying to get all of the favourites from a database to show up in a tableview. However, when I favourite a user, it only displays one.
The structure of my database looks like:
Favourites:
UserID:
key: User 1
key: User 2
and my code:
var usersArray[UserClass]()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
fetchFavourites()
}
func fetchFavourites () {
let userID = Auth.auth().currentUser?.uid
let favouriteRef = self.databaseRef.child("favourites").child(userID!)
favouriteRef.queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let favouriteID = "\(snapshot.value!)"
let usersRef = self.databaseRef.child("users")
usersRef.observe(.value, with: { (users) in
self.usersArray.removeAll()
for user in users.children {
let user = UserClass(snapshot: user as! DataSnapshot)
if favouriteID == user.uid {
self.usersArray.append(user)
}
self.tableView.reloadData()
}
})
})
}
I have tried creating another array within the function and then making the main array = to that, however it doesn't update the tableview when switching between screens. This code only works when the page view is loaded for the first time. However, I am using a back button that doesn't up date the screen.
Thanks, let me know if you want me to provide anything else.
Edited code (new problem, see below):
func fetchFavourites () {
DispatchQueue.main.async( execute: {
let userID = Auth.auth().currentUser?.uid
var tempFav = [UserClass]()
let favouriteRef = self.databaseRef.child("favourites").child(userID!)
favouriteRef.queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
let favouriteID = "\(snapshot.value!)"
let usersRef = self.databaseRef.child("users")
tempFav.removeAll()
usersRef.observe(.value, with: { (users) in
self.usersArray.removeAll()
for user in users.children {
let user = UserClass(snapshot: user as! DataSnapshot)
if favouriteID == user.uid {
tempFav.append(user)
}
self.usersArray = tempFav
self.tableView.reloadData()
}
})
})
})
}
The problem has been fixed where it populates the tableview with all of the liked users and gets rid of them if I unlike them etc. However, if I unlike all of the users. The array duplicates a user twice even though it should be empty.

Related

How can you ensure that cells are not duplicated when the device is overwhelmed by code from the previous page?

I have a problem that only occurs when page A has to run a massive amount of code and the user goes to page B before all the A page code is finished. In these instances, sometimes cells get duplicated(ie, say page B must be : User H in top, user F below him. Instead there are two Hs followed by two Fs below them).
Below is the relevant code of page B, but I am fairly certain the problem does not lie there. Why?: Because I changed the array that gets displayed [H,F] to a set, so according to the code, there should never be an instance like [H,H,F,F]
///Part1: code that gets called from viewdidLoad
var peppi = [Usery]()
func printPersonInfo(uid: String) {
self.handleA = thisUser.observe(DataEventType.value, with: { snapshot in
...
myPeopleRef44.queryLimited(toLast: 30).observeSingleEvent(of: .value, with: { [self] snapshot in
let uniqueArray = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray))
for person in peopleArray where uid == person.value as? String {
...
func decode(autoId: String) -> TimeInterval {
}
return TimeInterval(exactly: timestamp)!
}
...
if Calendar.current.isDateInToday(date){
let p = Usery(...)
peppi.append(p)
}
}
DispatchQueue.main.async {
self.peppi.sort { ($0.self.time1 ?? 0) > ($1.self.time1 ?? 0)
}
print(self.peppi, "lo")
self.table.reloadData()
}
})
})
}
/// Part: 2 In viewDidLoad, code that calls the function printPersonInfo
handle = myPeopleRef.queryLimited(toLast: 30).observe(DataEventType.value, with: { snapshot in
func decode(autoId: String) -> TimeInterval {
..
return …
}
let uniqueArray1 = snapshot.children.allObjects as! [DataSnapshot]
let peopleArray = Array(Set(uniqueArray1))
for person4 in peopleArray where uid == Auth.auth().currentUser?.uid {
self.dg.enter()
self.dg.leave()
self.dg.notify(queue: .main) {
let date = Date(timeIntervalSince1970: TimeInterval(time11)/1000.0)
print(date,"pdate")
if Calendar.current.isDateInToday(date){
self.printPersonInfo(uid: personUid)
}
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
let ref = Database.database().reference().child("people")
ref.removeObserver(withHandle: handle)
ref.removeObserver(withHandle: self.handleA)
}
})

'Invalid document reference. Document references must have an even number of segments' when trying to edit/delete new entry

I am able to delete the logged in users document when my app is first loaded. However, if I create a new entry, long press the new entry from the table view and select delete, I get a crash. I thought it had something to do with the document ID not being saved but I couldn't figure out why. If the same newly created entry is deleted after the app is closed and reopened then it will delete with no problem, but if I leave the app open and delete immediately after creating a new document, it will crash.
class BudgetViewController: UIViewController: {
var budgetData = [Transaction]()
func showAdd() {
let modalViewController = AddCategory()
modalViewController.addCategoryCompletion = { newCategories in
self.budgetData.append(newCategories)
self.tableView.reloadData()
}
modalViewController.modalPresentationStyle = .overFullScreen
modalViewController.modalTransitionStyle = .crossDissolve
modalViewController.selectionDelegate = self
present(modalViewController, animated: true, completion: nil)
}
#objc func handleLongPress(_ gestureRecognizer: UILongPressGestureRecognizer){
if gestureRecognizer.state == .began {
let touchPoint = gestureRecognizer.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint) {
let cell = CategoryCell()
var data = budgetData[indexPath.row]
let modalViewController = EditCategory()
modalViewController.deleteCategory = { row in
self.deletedRow = row
self.deleteRow()
}
modalViewController.documentID = data.trailingSubText ?? ""
modalViewController.modalPresentationStyle = .overFullScreen
modalViewController.modalTransitionStyle = .crossDissolve
present(modalViewController, animated: true, completion: nil)
modalViewController.row = indexPath.row
print("longpressed\(indexPath.row)\(data.trailingSubText)")
}
}
}
override func viewDidLoad() {
loadNewData()
}
func loadNewData() {
guard let user = Auth.auth().currentUser?.uid else { return }
db.collection("users").document(user).collection("Category").getDocuments() {
snapshot, error in
if let error = error {
print("\(error.localizedDescription)")
} else {
for document in snapshot!.documents {
let data = document.data()
let title = data["title"] as? String ?? ""
let uid = data["uid"] as? String ?? ""
let documentID = document.documentID
// let timeStamp = data["timeStamp"] as? Date
let newSourse = Transaction(title: title, dateInfo: "0% out of spent", image: UIImage.gymIcon, amount: 12, annualPercentageRate: 12, trailingSubText: documentID, uid: uid)
self.budgetData.append(newSourse)
}
self.tableView.reloadData()
}
}
}
class AddCategory: UIViewController {
#objc func saveAction(){
guard let uid = Auth.auth().currentUser?.uid else { return }
let newCategory = Transaction(title: textField.text ?? "", dateInfo: "0% out of spent", image: UIImage.gymIcon, amount: 12, annualPercentageRate: 23, trailingSubText: "", uid: uid)
db.collection("users").document(uid).collection("Category").addDocument(data: newCategory.dictionary)
self.dismiss(animated: false, completion: {
self.addCategoryCompletion?(newCategory)
})
self.dismiss(animated: false, completion: nil)
print("selected")
}
}
}
class EditCategory: UIViewController {
func deleteAction(){
guard let user = Auth.auth().currentUser?.uid else { return }
print("document::\(self.documentID)")
// let budget = textField.text
db.collection("users").document(user).collection("Category").document(documentID).delete { (err) in
if let err = err {
print(err.localizedDescription)
}else{
self.dismiss(animated: false, completion: {
self.deleteCategory?(self.row)
})
print("deleted successfully")
}
}
}
}
The error is strongly suggesting that user is nil or empty at the time you run this code:
guard let user = Auth.auth().currentUser?.uid else { return }
db.collection("users").document(user).collection("Category").getDocuments()
This almost certainly means that a user was not signed in at the time. Your code needs to check currentUser for nil before trying to access its uid property. nil means that no user is currently signed in.
The user will not be signed in immediately at app launch. You should use an auth state listener to get a callback when the user object becomes available.
Maybe not the best approach, but it is working now. I called
self.budgetData.removeAll()
self.loadNewData()
self.tableView.reloadData()
inside of my callback
modalViewController.addCategoryCompletion = { newCategories in
self.budgetData.append(newCategories)
self.tableView.reloadData()
}`
Based on the presented code, when a new category is added to Firebase
let newCategory = Transaction(title: textField.text ?? ...)
db.collection("users").document(uid).collection("Category").addDocument(data: newCategory
you're not getting a valid documentId from Firebase first. So therefore that object exists in your dataSource with no documentId so when you try to remove it, there's a crash.
A couple of options
Option 1: Create a firebase reference first, which will provide a Firebase documentId that you can add to the object when writing. See the docs. Like this
let newCategoryRef = db.collection("Category").document()
let docId = newCategoryRef.documentId
...add docId to the category object, then add to dataSource
or
Option 2: Add an observer to the node (see Realtime Updates) so when a new document is written, the observers event will fire and present the newly added document, which will contain a valid documentId, and then craft an category object based on that data and add that object to your dataSource array. In this case, you don't need to add it to the dataSource array when writing as it will auto-add after it's written based on the observers .added event.

How to check unique username in Firebase Database (swift)

I've Firebase Database where each user has own email and username. How to check unique username? I tried to make it like this, but my code doesn't work properly therefore different users can have the same username
usernameField.isHidden = false
let username:String = self.usernameField.text!
if (usernameField.text?.isEmpty == false){
ref.child("users").queryOrdered(byChild("username").queryEqual(toValue: username).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
print("username exist")
}else{
ref.root.child("users").child(userID).updateChildValues(["username": username])
}
})
}
I'm a little bit newbie in Firebase I store email and username for each user like this newUserReference.setValue(["username":String(), "email" : self.emailTextField.text!]). On next view, user can type username in usernameField.text and this value will be added in Firebase Database. But if the next user (user 2) will type the same username like previous user, it must be blocked, because username should be unique
You still need to indicate what property you want to order/filter on with queryOrdered(byChild:):
if (usernameField.text?.isEmpty == false){
ref.child("users").queryOrdered(byChild:"username").queryEqual(toValue: username).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
If you're trying to store your user's id on login do this when you receive a successful response to the login:
create a Shared Instance to store the ID
class userDataSource {
var id : String? // variable to store your users ID
static let sharedInstance = PageDataSource() // global access to this dataSource
private init() {}
}
Assign the id a value after successful login
func getIDFromLogin() {
if let user = Auth.auth().currentUser {
print(user.uid)
userDataSource.sharedInstance.id = user.uid
}
}
Then you can do this to view each id:
ref.child("users").observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print(snap.key) // you can compare userDataSource.sharedInstance.id to this value
}
}
})
Or if you just want that user's data do this:
ref.child("users").child(userDataSource.sharedInstance.id!).observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
print(snap)
}
}
})
Edit to answer your question more accurately
Here is an answer more inline with your question. First thing I will recommend is for you to add a table to Firebase that only contains the usernames, and the .uid's that they belong to. You will need to first read through that table to make sure that no one else has that username, then update the table accordingly:
// This function will check through all of the usernames and return a true or false value in the completion handler
func checkUsernames(_ completion: #escaping(_ success: Bool) -> Void) {
ref.child("usernames").observeSingleEvent(of: .value, with: { snapshot in
if let snapshots = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshots {
if snap.value == username {
completion(false)
}
}
completion(true)
} else {
completion(true) // TODO: check for errors before setting completion
}
})
}
// this function will set the username values in Firebase
func storeUsername() {
let usernameRef = ref.child("usernames")
usernameRef.updateChildValues(["\(userDataSource.sharedInstance.id!)" : username])
}
}
}
Assuming you have already handled your username variable and set it's value, you will call the functions like this:
checkUsernames({ (success) in
if success {
storeUsername()
// you may also want to update your "users" table here as well
} else { print("Duplicate Username") } // handle alert or something here
})

Completion handler Firebase observer in Swift

I am making a completion handler for a function which will return a list of objects. When it return value for first time, it works well. But when any change happen into firebase database and again observe gets called, array size gets doubled up. Why it's getting doubled up?
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
And calling like this
getStadiums(){ stadiums
print(stadiums.count) // count gets doubled up after every observe call
}
The code you're using declares stadiums outside of the observer. This means any time a change is made to the value of the database reference, you're appending the data onto stadiums without clearing what was there before. Make sure to remove the data from stadiums before appending the snapshots again:
func getStadiums(complition: #escaping ([Stadium]) -> Void){
var stadiums: [Stadium] = []
let stadiumRef = Database.database().reference().child("Stadium")
stadiumRef.observe(.value, with: { (snapshot) in
stadiums.removeAll() // start with an empty array
for snap in snapshot.children {
guard let stadiumSnap = snap as? DataSnapshot else {
print("Something wrong with Firebase DataSnapshot")
complition(stadiums)
return
}
let stadium = Stadium(snap: stadiumSnap)
stadiums.append(stadium)
}
complition(stadiums)
})
}
This line stadiumRef.observe(.value, with: { (snapshot) in ... actually adding an observer that will be called everytime your stadium data is changed.
Because you called it twice by using getStadiums(){ stadiums ..., the total observer added will be 2.
That makes the line stadiums.append(stadium) called twice in the second call.
My suggestion would be to use stadiumRef.observe() once without calling it from getStadiums().
Create a Model as below
class OrderListModel: NSObject {
var Order:String?
var Date:String?
}
Use the below code in the view controller and you should be able to see content in your tableview
func getOrdersData() {
self.orderListArr.removeAll()
let ref = Database.database().reference().child(“users”).child(user).child("Orders")
ref.observe(.childAdded, with: { (snapshot) in
print(snapshot)
guard let dictionary = snapshot.value as? [String : AnyObject] else {
return
}
let orderObj = OrderModel()
orderObj.Order = dictionary[“Order”] as? String
orderObj.Date = dictionary[“Date”] as? String
self.orderListArr.append(orderObj)
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.reloadData()
}, withCancel: nil)
}
func ListenForChildrenAdded() {
let registerToListenTo = "YourPathHere"
ref.child(registerToListenTo).observeSingleEvent(of: .value) { (snapshot) in
let initialChildren = snapshot.childrenCount
var incrementer = 0
ref.child(registerToListenTo).observe(.childAdded, with: { (snapshot) in
incrementer += 1
print("snapshot: \(snapshot.key) #\(incrementer)")
if incrementer == initialChildren {
print("-> All children found")
} else if incrementer > initialChildren {
print("-> Child Was Added - Run Some Code Here")
}
})
}}

Wont show like until refresh of tableview - swift & Firebase

I am fetching all the posts from the users I am following like this:
func observePosts(userID: String) {
let ref = FIRDatabase.database().reference().child("Users").child(userID).child("Wall")
ref.observeEventType(.ChildAdded, withBlock: { (snapshot) in
let postId = snapshot.key
let postReference = FIRDatabase.database().reference().child("feed-items").child(postId)
postReference.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
let update = Sweet(snapshot: snapshot)
self.updates.append(update)
self.updates = self.updates.reverse()
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}, withCancelBlock: nil)
}, withCancelBlock: nil)
}
THe problem is, when I click like on one of the posts, the likelabel is not updates. I have a custom class for the cell and in that I have the likeButton action and it is updating the count of likes. It just does not show in the tableView until I refresh the tableview.
How can I get it update the count in real time so I do not need to pull to refresh before I can see that I liked the post?

Resources