I have a tableView with data populated from Firebase, when I click a delete button the data is removed from Firebase but it remains on my app and it doesn't remove the data from the tableView until I close the app and reopen it. Here is how I set up the delete function:
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
}
}
})
self.TableView.reloadData()
}
Here are the delegate and datasource:
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
posts.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectedIndex = indexPath
self.didExpandCell()
if isExpanded && self.selectedIndex == indexPath{
print(indexPath)
} else{
}}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if isExpanded && self.selectedIndex == indexPath{
return 300
}
return 126
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath) as! ProfileTableViewCell
//Configure the cell
let post = self.posts[indexPath.row] as! [String: AnyObject]
cell.Title.text = post["title"] as? String
cell.Author.text = post["Author"] as? String
cell.ISBN10.text = post["ISBN10"] as? String
return cell
}
I attempted to add a tableview.reloaddata at the end of the function but that doesn't help. What am I doing wrong?
Remove your object from posts array and then reload your tableView in main queue one you remove your object from firebase.
Check below code:
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
} else {
//Here remove your object from table array
self.posts.remove(at: indexPath?.row)
//Reload your tableview in main queue
DispatchQueue.main.async{
self.TableView.reloadData()
}
}
}
})
}
Didn't tested it so let me know if you still have issue with above code.
func deletePost() {
let uid = FIRAuth.auth()!.currentUser!.uid
let storage = FIRStorage.storage().reference(forURL: "gs://gsignme-14416.appspot.com")
FIRDatabase.database().reference().child("posts").child(uid).observe(.childAdded, with: { (snapshot) in
let indexPath = self.selectedIndex
let post = self.posts[(indexPath?.row)!] as! [String: AnyObject]
self.key = post["postID"] as? String
self.itemsRef = FIRDatabase.database().reference().child("posts").child(uid).child(self.key!)
// Remove the post from the DB
FIRDatabase.database().reference().child("books").child(self.key!).removeValue { error in
if error != nil {
print("error \(error)")
return // use return here or use if else
}
// no error has occurred,hence move on to remove the post from the array
self.posts.remove(at: indexPath.row)
} })
DispatchQueue.main.async {
self.TableView.reloadData()
}
}
Delete the removed object from your array also. Your table view gets populated using posts array, not from Firebase object itself. when you click delete button the data is removed from Firebase but it remains on your app, as you have not deleted that object from your array i.e. posts thats why it doesn't remove the data from the tableView until you close the app and reopen it.
You need the remove the deleted object from your posts array and reload that rows to get the effect.
self.posts.remove(at: indexPath.row)
and then reload that specific row itself.
self.tableView.beginUpdates
tableView.reloadRows(at: [indexPath], with: .top)
self.tableView.endUpdates;
In your case
FIRDatabase.database().reference().child("books").child(self.key!).
removeValue { error in
if error != nil {
print("error \(error)")
} else{
self.posts.remove(at: indexPath.row)
self.tableView.beginUpdates
tableView.reloadRows(at: [indexPath], with: .top)
self.tableView.endUpdates;
}
}
})
Hope it helps. Happy Coding!!
Related
I am using firebase realtime database and implementing user profile data with usersFriend and location. I need to implement the update in object array and show updated values in tableview. I have tried but I am not successful in updating object and then tableview reload. Function already developed.
I need to show updated object array swapped with new values and display in tableview.
var myFriendsDataSource = [FriendClass]()
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("profiles") usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.batteryStatus), updating")
self.myFriendsDataSource[friendIndex] = friend
self.tableView.reloadData()
}
})
}
Class:
class FriendClass {
var uid = ""
var name = ""
var batteryStatus = Int()
var latitude = Double()
var longitude = Double()
var timeStamp = Int64()
//var profilePic
init(withSnapshot: DataSnapshot) {
self.uid = withSnapshot.key
self.name = withSnapshot.childSnapshot(forPath: "name").value as? String ?? "No Name"
self.batteryStatus = withSnapshot.childSnapshot(forPath: "batteryStatus").value as? Int ?? 0
self.latitude = withSnapshot.childSnapshot(forPath: "latitude").value as? Double ?? 0.0
self.longitude = withSnapshot.childSnapshot(forPath: "longitude").value as? Double ?? 0.0
self.timeStamp = withSnapshot.childSnapshot(forPath: "timeStamp").value as? Int64 ?? 0
}
}
Updated:
func loadUsersFriends() {
let uid = "zzV6DQSXUyUkPHgENDbZ9EjXVBj2"
let myFriendsRef = self.ref.child("userFriends").child(uid)
myFriendsRef.observeSingleEvent(of: .value, with: { snapshot in
let uidArray = snapshot.children.allObjects as! [DataSnapshot]
for friendsUid in uidArray {
self.loadFriend(withUid: friendsUid.key)
print(friendsUid)
}
})
}
func loadFriend(withUid: String) {
let thisUserRef = self.ref.child("profiles").child(withUid)
thisUserRef.observeSingleEvent(of: .value, with: { snapshot in
let aFriend = FriendClass(withSnapshot: snapshot)
self.myFriendsDataSource.append(aFriend)
print(self.myFriendsDataSource)
self.tableView.reloadData()
self.watchForChangesInMyFriends()
})
}
Update 2:
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 10
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myFriendsDataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FriendListTableViewCell", for: indexPath) as! FriendListTableViewCell
let dic = myFriendsDataSource[indexPath.row]
cell.frndName.text = dic.name
return cell
}
Given the above comment discussion, I think you need to update your watchForChangesInMyFriends method as below to actually update the datasource with the new friend data. You should also do all your UI updates on the main thread, and as there is no guarantee that this closure will run on the main thread you need to force the tableView update onto the main thread.
func watchForChangesInMyFriends() {
let usersRef = self.ref.child("profiles") usersRef.observe(.childChanged, with: { snapshot in
let key = snapshot.key
if let friendIndex = self.myFriendsDataSource.firstIndex(where: { $0.uid == key} ) {
let friend = self.myFriendsDataSource[friendIndex]
print("found user \(friend.batteryStatus), updating")
self.myFriendsDataSource[friendIndex] = FriendClass(withSnaphot: snapshot)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
It's also better practice to update just the tableView data that has changed rather than reloading the whole tableView. You can probably use the array index to generate an IndexPath for the appropriate row and then just reload that row. Without seeing your tableView methods I can't be precise, but it'll probably look something like this:
let indexPath = IndexPath(row: friendIndex, section: 0)
DispatchQueue.main.async {
self.tableView.reloadRows(at: [indexPath], with: .automatic)
}
people, I have this issue when I try back image from different cell
(Thread 1: Fatal error: Index out of range)
what I'm doing here ?
I'm trying to build an Instagram clone and in my home view controller that what should posts show up. I make navigation with a table view and that table view has 2 cell with the different identifier. cell number 1 it's a header that brings data from users table to my username label and profile image. and cell number 2 its for posts its should bring post data like image and caption. I use firebase database.
my code :
import UIKit
import FirebaseAuth
import FirebaseDatabase
class HomeViewController: UIViewController ,UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
var posts = [Post]()
var users = [UserD]()
override func viewDidLoad() {
super.viewDidLoad()
tableview.dataSource = self
loadposts()
userDetal()
// var post = Post(captiontxt: "test", photoUrlString: "urll")
// print(post.caption)
// print(post.photoUrl)
}
func loadposts() {
Database.database().reference().child("posts").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let captiontxt = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let post = Post(captiontxt: captiontxt, photoUrlString: photoUrlString)
self.posts.append(post)
print(self.posts)
self.tableview.reloadData()
}
}
}
func userDetal() {
Database.database().reference().child("users").observe(.childAdded){ (snapshot: DataSnapshot)in
print(Thread.isMainThread)
if let dict = snapshot.value as? [String: Any]{
let usernametxt = dict["username"] as! String
let profileImageUrlString = dict["profileImageUrl"] as! String
let user = UserD(usernametxt: usernametxt, profileImageUrlString: profileImageUrlString)
self.users.append(user)
print(self.users)
self.tableview.reloadData()
}
}
}
#IBAction func logout(_ sender: Any) {
do {
try Auth.auth().signOut()
}catch let logoutErrorr{
print(logoutErrorr)
}
let storyboard = UIStoryboard(name: "Start", bundle: nil)
let signinVC = storyboard.instantiateViewController(withIdentifier: "SigninViewController")
self.present(signinVC, animated: true, completion: nil)
}
}
extension HomeViewController: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell = tableview.dequeueReusableCell(withIdentifier: "imagecell", for: indexPath) as! PostCellTableViewCell
cell.postimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.captionLabel.text = posts[indexPath.row].caption
let photoUrl = posts[indexPath.row].photoUrl
getImage(url: photoUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.postimage.image = photo
}
}
}
}
return cell
} else if indexPath.row == 1 {
let cell = tableview.dequeueReusableCell(withIdentifier: "postcell", for: indexPath) as! HeaderTableViewCell
cell.userimage.image = nil
cell.tag += 1
let tag = cell.tag
cell.usernamelabel.text = users[indexPath.row].username
//Error showing here????????????????????????????????????
let profileImageUrl = users[indexPath.row].profileImageUrl
getImage(url: profileImageUrl) { photo in
if photo != nil {
if cell.tag == tag {
DispatchQueue.main.async {
cell.userimage.image = photo
}
}
}
}
return cell
}
return UITableViewCell()
}
func getImage(url: String, completion: #escaping (UIImage?) -> ()) {
URLSession.shared.dataTask(with: URL(string: url)!) { data, response, error in
if error == nil {
completion(UIImage(data: data!))
} else {
completion(nil)
}
}.resume()
}
}
try this one.
cell.tag = indexpath.row
What is the content of users array ?
Are you sure you want to define as many sections as users or as many rows ?
In this case use
func numberOfRows(in tableView: NSTableView) -> Int {
return users.count
}
As explained, you need to rewrite completely cellForRowAt
It should look like this :
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if row < users.count {
let user = users[row]
if let cellView = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "CellID"), owner: self) {
(cellView as! NSTableCellView).textField?.stringValue = user.name
// do the same for all the fields you need to set
return cellView
} else {
return nil
}
}
return nil
}
thanx, my friend, I found a good way to contain my cell. for post cell, i just use cellForRowAt and but the post data. for header cell i use viewForHeaderInSection
and but my user data with heightForHeaderInSection. to make the high for a view
My tableview currently updates my table and adds new items in real-time when they are added to my firebase database. The problem is that I cannot delete in real-time. I am storing my data from firebase in a local array, and then loading that array to the tableview.
I tried to condense my code a bit. I also tried to put the Firebase code that is inside my removeDeletedItems() function inside my populateArrays() function, and to put it after the .childAdded listener, but did not have luck with deleting the data in real-time.
override func viewDidLoad() {
super.viewDidLoad()
populateArrays()
}
func removeDeletedItems() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observe(FIRDataEventType.childRemoved, with: { (FIRDataSnapshot) in
guard let emailToFind = FIRDataSnapshot.value as? String else { return }
for (index, email) in self.usernames.enumerated() {
if email == emailToFind {
let indexPath = IndexPath(row: index, section: 0)
self.usernames.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
self.tableView.reloadData()
}
}
})
}
func populateArrays(){
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Users").observe(FIRDataEventType.childAdded, with: { (FIRDataSnapshot) in
if let data = FIRDataSnapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
self.usernames.append(name)
self.removeDeletedItems()
self.tableView.reloadData()
}
}
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = usernames[indexPath.row]
return cell
}
Isn't the observed value always a dictionary? And shouldn't you check also for the name rather than the email?
The loop to find the name is not needed. There is a convenience function.
databaseRef.child("Users").observe(FIRDataEventType.childRemoved, with: { snapshot in
guard let data = snapshot.value as? [String:Any],
let nameToFind = data[Constants.NAME] as? String else { return }
if let index = self.usernames.index(of: nameToFind) {
let indexPath = IndexPath(row: index, section: 0)
self.usernames.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
// don't reload the table view after calling `deleteRows`
}
}
})
In the code below, I query for all new posts using the posted date and childAdded, but it seems that when there isn't a new item, the table view doesn't show any items but the firebase block still runs? Please help. I did add number of rows in section. I don't know why stack overflow won't show.
FIRDatabase.database().reference().child("posts").child(uid!).queryOrdered(byChild: "postedDate").observe(.childAdded, with: {(snapshot) in
if (snapshot.hasChildren()) {
print("Has children")
if let dictionary = snapshot.value as? [String: AnyObject] {
let imageCount = dictionary["Images"]?.count
// Post not saved
if (imageCount == nil) {
print("image count is nil")
}
// better catch
else if (imageCount! < 3) {
print("not all 3 were posted")
}
// Saved post
else {
// Adding to details array
self.keyArray.append(snapshot.key)
// Inside a for loop to get every Image
for index in 1 ..< dictionary["Images"]!.count + 1 {
// Check = image1, image2 ...
let check = "image" + String(index)
// Put into the imageArray dictionary
self.imageArray[check] = dictionary["Images"]?[check] as! String?
}
// Handles adding the key and appending the image
self.imageArray2[snapshot.key] = self.imageArray
self.imageArray3.append(self.imageArray2)
// No images but actual values
// Creating makeEasy variables
var valuesArray = [String: String]()
// Adding values to specific array
let name = dictionary["Itemname"] as? String?
// I have all items
if (name != nil) {
// Add values to valueArray
valuesArray["name"] = name!
// Add the Key: UUID
self.details[snapshot.key] = valuesArray
// Then put in the actual array
self.detailsArr.append(self.details)
}
// I DONT have all items
else {
print("Not present!!")
}
// Removing
self.imageArray.removeAll()
self.imageArray2.removeAll()
valuesArray.removeAll()
self.details.removeAll()
}
}
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
else {
print("Has NO children")
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.imageArray3.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "viewTableCell", for: indexPath) as! viewTableViewCell
// get the key first and CHECK IF IT EXISTS ******
let key = keyArray[indexPath.row]
cell.name.text = self.detailsArr[indexPath.row][key]!["name"]
// cache the pic
if let pic = self.imageArray3[indexPath.row][key]?["image1"] {
cell.itemImage.load(pic)
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the key and the index
self.tappedIndex = indexPath.row
self.currentKey = keyArray[indexPath.row]
self.performSegue(withIdentifier: "mystify", sender: self)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 400
}
guys I am getting data from Foursquare APi and here is my code below.
But I am getting a nil error at cellForRowAtIndexPath that venueItems is nil
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Table View
self.tableView = UITableView()
// Location Manager Stuff
self.locationManager = CLLocationManager()
self.locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
self.locationManager.delegate = self
let status = CLLocationManager.authorizationStatus()
if status == .notDetermined {
self.locationManager.requestWhenInUseAuthorization()
} else if status == CLAuthorizationStatus.authorizedWhenInUse
|| status == CLAuthorizationStatus.authorizedAlways {
self.locationManager.startUpdatingLocation()
} else {
showNoPermissionsAlert()
}
exploreVenues()
}
// Func's
func exploreVenues() {
guard let location = self.locationManager.location else {
return
}
var parameters = [Parameter.query: "Pubs"]
parameters += location.parameters()
let task = self.session.venues.explore(parameters) {
(result) -> Void in
if self.venueItems != nil {
return
}
if !Thread.isMainThread {
fatalError("!!!")
}
if let response = result.response {
if let groups = response["groups"] as? [[String: AnyObject]] {
var venues = [[String: AnyObject]]()
for group in groups {
if let items = group["items"] as? [[String: AnyObject]] {
venues += items
}
}
self.venueItems = venues
}
self.tableView.reloadData()
} else if let error = result.error, !result.isCancelled() {
self.showErrorAlert(error)
}
}
task.start()
}
// Table View Data source
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let venueItems = self.venueItems {
return venueItems.count
}
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! VenueTableViewCell
// This is where the error occurs
let item = self.venueItems![(indexPath as NSIndexPath).row] as JSONParameters!
self.configureCellWithItem(cell, item: item!)
return cell
}
func configureCellWithItem(_ cell: VenueTableViewCell, item: JSONParameters) {
if let venueInfo = item["venue"] as? JSONParameters {
cell.nameLabel.text = venueInfo["name"] as? String
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = cell as! VenueTableViewCell
let tips = self.venueItems![(indexPath as NSIndexPath).row]["tips"] as? [JSONParameters]
guard let tip = tips?.first, let user = tip["user"] as? JSONParameters,
let photo = user["photo"] as? JSONParameters else {
return
}
let URL = photoURLFromJSONObject(photo)
if let imageData = session.cachedImageDataForURL(URL) {
cell.venueImageView.image = UIImage(data: imageData)
} else {
cell.venueImageView.image = nil
session.downloadImageAtURL(URL) { (imageData, error) -> Void in
let cell = tableView.cellForRow(at: indexPath) as? VenueTableViewCell
if let cell = cell, let imageData = imageData {
let image = UIImage(data: imageData)
cell.venueImageView.image = image
}
}
}
}
}
I am quite new to programming personally I think that the venueItems is nil because the cellForRowAtIndexPath is being executed first. If this is the error how can I fix it so the code in cellForRowAtIndexpath runs after my venueItems has a value.. or any other more efficient Way?
Your numberOfRowsInSection returns 10 when self.venueItems is nil. self.venueItems appears to be nil until your network request finishes so the table view, having been told it has 10 rows to display asks for a cell for each row. You then attempt to force unwrap an optional property (self.venueItems!) and crash.
It looks like your self.venueItems is an optional for good reason, don't discard that information with a force unwrap (!). You could either return 0 rows when this property is nil or initialize it to a non-optional empty array which you could then always ask for its count.
In general with this sort of problem you don't want to focus on preventing cellForRowAtIndexPath from being called but rather plan for it to be called at any point and return a reasonable result (like reporting that the table has 0 rows) when your background tasks haven't finished yet.