Deleted Table View Cell Copying Remaining Tableview Cell Instead of Disappearing - ios

I've got a UITableView that pulls some information from a Firebase Realtime Database. The information all gets pulled and populated properly, and deletes when it is supposed to, but I get a weird bug. If there are multiple cells in the table view, when I delete one of the cells, instead of that cell disappearing, it just gets replaced with a copy of one of the remaining cells. When I then close the table view and reopen it, everything is correct (i.e. the copied cell is gone). I have included some photos of this below as an illustration. Here is the swift file for the table view:
import UIKit
import Firebase
import FirebaseAuth
class SpotRemove: ViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tblSpots: UITableView!
var spotsList = [ArtistModel]()
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return spotsList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let spot: ArtistModel
spot = spotsList[indexPath.row]
cell.lblName.text = spot.type
cell.lblGenre.text = spot.avail
cell.lblPrice.text = spot.price
return cell
}
var refSpots: DatabaseReference?
override func viewDidLoad() {
super.viewDidLoad()
tblSpots.allowsMultipleSelectionDuringEditing = true
refSpots = Database.database().reference().child("Spots")
refSpots?.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for spots in snapshot.children.allObjects as![DataSnapshot]{
let spotKey = spots.key
let spotObject = spots.value as? [String: AnyObject]
let spotType = spotObject?["Type"]
let spotAvail = spotObject?["Availability"]
let spotPrice = spotObject?["Price"]
let spotID = spotObject?["UserID"]
let spot = ArtistModel(id: spotID as! String?, avail: spotAvail as! String?, type: spotType as! String?, price: spotPrice as! String?, key: spotKey as! String?)
let userID = Auth.auth().currentUser?.uid
if userID == spotID as? String {
self.spotsList.append(spot)
}
}
self.tblSpots.reloadData()
}
})
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let spot = self.spotsList[indexPath.row]
if let spotKey = spot.key {
Database.database().reference().child("Spots").child(spotKey).removeValue { (error, ref) in
if error != nil {
print("Failed to delete message:", error!)
return
}
self.spotsList.remove(at: indexPath.row)
self.tblSpots.deleteRows(at: [indexPath], with: .automatic)
self.tblSpots.reloadData()
}
tblSpots.reloadData()
}
}
}
I believe the issue is that I'm calling reloadData() without changing numberOfRowsInSection. Would the fix be as simple as putting something like numberOfRowsInSection = numberOfRowsInSection - 1 right before I call reload data? Thank you all in advance.
the two original cells
the two cells after deletion
The reloaded table view showing only the remaining cell (as it is supposed to)

It's a round-about, brute force way, but I decided to have the array and the table view clear all contents then reload them from scratch. This works (but is not perfect for optimizing efficiency). The whole delete function now looks like this:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let spot = self.spotsList[indexPath.row]
if let spotKey = spot.key {
Database.database().reference().child("Spots").child(spotKey).removeValue { (error, ref) in
if error != nil {
print("Failed to delete message:", error!)
return
}
self.spotsList.removeAll()
self.tblSpots.reloadData()
self.refSpots = Database.database().reference().child("Spots")
self.refSpots?.observe(.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for spots in snapshot.children.allObjects as![DataSnapshot]{
let spotKey = spots.key
let spotObject = spots.value as? [String: AnyObject]
let spotType = spotObject?["Type"]
let spotAvail = spotObject?["Availability"]
let spotPrice = spotObject?["Price"]
let spotID = spotObject?["UserID"]
let spot = ArtistModel(id: spotID as! String?, avail: spotAvail as! String?, type: spotType as! String?, price: spotPrice as! String?, key: spotKey as! String?)
let userID = Auth.auth().currentUser?.uid
if userID == spotID as? String {
self.spotsList.append(spot)
}
}
self.tblSpots.reloadData()
}
})
}
}
}

Related

How to Sort TableViewCells by date string

As shown below I want to sort my TableViewCells by the date. For this I have the time which is also called timestampName.
Right before I reload the data, I tried to sort it, but somehow this has no effect. It also throws me a warning, that I dont use the result of the sorted by. I understand this, but I dont know how to fix that.
import UIKit
import Firebase
class popularViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet var table: UITableView!
// var models = [PhotoPost]()
var texttt = [TextPost]()
override func viewDidLoad() {
super.viewDidLoad()
gettingPosts()
table.register(popularTableViewCell.nib(), forCellReuseIdentifier: popularTableViewCell.identifier)
table.register(featuredTableViewCell.nib(), forCellReuseIdentifier: featuredTableViewCell.identifier)
table.register(textTableViewCell.nib(), forCellReuseIdentifier: textTableViewCell.identifier)
table.delegate = self
table.dataSource = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return texttt.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: textTableViewCell.identifier, for: indexPath) as! textTableViewCell
cell.configure(with: self.texttt[indexPath.row])
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 300
}
func gettingPosts(){
let db = Firestore.firestore()
let postsRef = db.collection("posts")
postsRef.addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added){
let data = diff.document.data()
let Name = data["username"] as! String
let text = data["description"] as! String
let likes = data["likes"] as! Int
let typ = data["postType"] as! Int
let pfp = data["profileImage"] as! String
let uid = data["uid"] as! String
let pic = data["picture"]
let time = data["time"] as! String
if typ == 0{ // Text post
let dasDing = TextPost(numberOfComments: 0, username: Name, timestampName: time, userImageName: pfp, textName: text)
self.texttt.append(dasDing)
self.texttt.sorted(by: { $0.timestampName < $1.timestampName }) //WARNING: Result of call to 'sorted(by:)' is unused
self.table.reloadData()
}
struct TextPost {
let numberOfComments: Int
let username: String
let timestampName: String
let userImageName: String
let textName: String
}
Use sort instead of sorted. The sorted method returns a new sorted array, on the other hand, the sort method sorts the array on which it was called.
self.texttt.sort(by: { $0.timestampName < $1.timestampName })
This should also work, using sorted:
self.texttt = self.texttt.sorted(by: { $0.timestampName < $1.timestampName })

First row in tableview doesn't disappear after complete or delete action

I have a tableView that is populated when a user adds a record. This data is stored in the real-time firebase. Swiping right allows the user to complete the task and moves the record from the repair child to the complete child. Swiping left allows the user to delete the row. In both cases the row is removed from the table. This works fine for every row except for the first. When the user performs either of these actions the real time database performs the functions correctly; however, the first row retains the data. What am I missing?
override func viewDidLoad() {
super.viewDidLoad()
self.doListTableView.delegate = self
self.doListTableView.dataSource = self
//implement Google ADMobs
// In this case, we instantiate the banner with desired ad size.
bannerView = GADBannerView(adSize: kGADAdSizeBanner)
addBannerViewToView(bannerView)
bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716"
bannerView.rootViewController = self
bannerView.load(GADRequest())
bannerView.delegate = self
//get reference to firebase database
refRepairs = Database.database().reference().child("repairs").child(UserID!);
//observing the data changes
refRepairs!.observe(DataEventType.value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.repairList.removeAll()
//iterating through all the values
for repairs in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let repairObject = repairs.value as? [String: AnyObject]
let brand = repairObject?["brand"]
let id = repairObject?["id"]
let categories = repairObject?["categories"]
let modelNumber = repairObject?["modelNumber"]
//creating artist object with model and fetched values
let repair = RepairModel(id: id as! String?, categories: categories as! String?, brand: brand as! String?, modelNumber: modelNumber as! String?)
self.repairList.append(repair)
}
//reloading the tableview
self.doListTableView.reloadData()
}
})
}
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let complete = UIContextualAction.init(style: .normal, title: "Complete") { (action, view, completion) in
let repairList = self.repairList[indexPath.row]
if let selectedRepairItem = repairList.id {
Database.database().reference().child("completed").child(self.UserID!).child(selectedRepairItem).setValue(["id": repairList.id, "brand": repairList.brand, "categories": repairList.categories, "modelNumber": repairList.modelNumber])
Database.database().reference().child("repairs").child(self.UserID!).child(selectedRepairItem).removeValue()
}
self.doListTableView.reloadData()
}
// complete.image = //... Add an image
complete.backgroundColor = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
let action = UISwipeActionsConfiguration.init(actions: [complete])
action.performsFirstActionWithFullSwipe = true //... Full swipe support
return action
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let repairList = self.repairList[indexPath.row]
if let selectedRepairItem = repairList.id {
Database.database().reference().child("repairs").child(UserID!).child(selectedRepairItem).removeValue()
self.doListTableView.reloadData()
}
}
I had this working great but I ran into an issue with signing out. I fixed the issue but now I have to figure this out. Not sure why this is happening. Thanks in advance.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//print(repairList.count)
return (repairList.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! RepairTableViewCell
let repair: RepairModel
repair = repairList[indexPath.row]
cell.labelBrand.text = repair.brand
cell.labelModelNumber.text = repair.modelNumber
return(cell)
}
Here's my model:
class RepairModel {
var id: String?
var categories: String?
var brand: String?
var modelNumber: String?
init(id: String?, categories: String?, brand: String?, modelNumber: String?){
self.id = id
self.categories = categories
self.brand = brand
self.modelNumber = modelNumber
}
}

Tapped cell in UITableView returns label text from last cell out of an array not the chosen one

I have fixed my earlier problem and have now worked out where the main problem is, I am pulling in a json array with alamofire but am not sure how to properly move the data from one viewcontroller to another. If I hardcode the array with var name = ["Hello", "Goodbye"] I can get it to work but am not sure how to do it with the json. Thank you to any and all help.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let URL_GET_DATA = "http://www.localnewsplus.com.au/ios/service.php"
#IBOutlet weak var tableViewHeroes: UITableView!
var heroes = [Hero]()
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return heroes.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let hero: Hero
hero = heroes[indexPath.row]
cell.labelName.text = hero.name
cell.labelTeam.text = hero.team
Alamofire.request(hero.imageUrl!).responseImage { response in
if let image = response.result.value {
cell.heroImage.image = image
}
}
//cell.labelName.text = name[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(URL_GET_DATA).responseJSON { response in
if let json = response.result.value {
let heroesArray : NSArray = json as! NSArray
for i in 0..<heroesArray.count{
self.heroes.append(Hero(
name: (heroesArray[i] as AnyObject).value(forKey: "st_heading") as? String,
team: (heroesArray[i] as AnyObject).value(forKey: "st_modified") as? String,
imageUrl: (heroesArray[i] as AnyObject).value(forKey: "imageurl") as? String
))
}
self.tableViewHeroes.reloadData()
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "articleViewController") as? articleViewController
vc?.article_st_heading = name[indexPath.row]
self.navigationController?.pushViewController(vc!, animated: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
There are 2 ways to do this
Try to get data from the array which you used in cellForRow to populate data
Let text = someArray[indexPath. Row]
Get the cell instead of the create new one in didSelect method
Let cell = table. CellForRowAt[indexPath ]
Let text = cell.text

tableview doesn't refresh deleted data

My table view does not refresh. it keep the old values of deleted data
and can show new added values.
it only refresh(remove deleted values) when i close the app and open it again.
here is my code
private func observeChannels() {
let userEmail = Auth.auth().currentUser?.email
channelRefHandle = channelRef.observe(.childChanged, with: { (snapshot) -> Void in
let channelData = snapshot.value as! Dictionary<String, AnyObject>
let id = snapshot.key
let groupimage = channelData["groupImage"] as? String!
let descc = channelData["desc"] as? String!
let groupCountvar = channelData["Members"]?.count
let groupTasksVar = channelData["Tasks"]?.count
if let name = channelData["name"] as! String!, name.characters.count > 0 {
//members snapshot
if let childSnapshot = snapshot.childSnapshot(forPath: "Members") as? DataSnapshot{
if let membersDictionary = childSnapshot.value as? [String:AnyObject] , membersDictionary.count > 0{
for membersDictionary in membersDictionary {
if userEmail == membersDictionary.value as? String {
self.groups.append(Group(id: id,name: name, createdBy: (userEmail)!, desc: (descc)!, groupImage: (groupimage)!, groupCount: ("\(groupCountvar!)") , groupTasksCount: ("\(groupCountvar!)")))
print(membersDictionary.value)
}
}
}
self.tableView.reloadData()
}}else {
print("Error!")
self.tableView.reloadData()
}
})}
datasource / delegate
override func numberOfSections(in tableView: UITableView) -> Int {
return 2 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let currentSection: Section = Section(rawValue: section) {
switch currentSection {
case .createNewChannelSection:
return 0
case .currentChannelsSection:
return groups.count
}
} else {
return 0 }
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 75.0 }
//tableView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ExistingChannel", for: indexPath) as! GroupTableViewCell
if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
cell.goupName?.text = groups[(indexPath as NSIndexPath).row].name
cell.groupDesc?.text = groups[(indexPath as NSIndexPath).row].desc
cell.groupimg?.image = UIImage(named: groups[(indexPath as NSIndexPath).row].groupImage)
cell.numbMembLbl?.text = groups[(indexPath as NSIndexPath).row].groupCount
cell.taskNumbLbl?.text = groups[(indexPath as NSIndexPath).row].groupTasksCount
}
return cell }
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if (indexPath as NSIndexPath).section == Section.currentChannelsSection.rawValue {
let channel = groups[(indexPath as NSIndexPath).row]
self.performSegue(withIdentifier: "ShowChannel", sender: channel)
} }
this is my table view i dont know why the deleted/removed doesn't observe.
You are observing the node for .childChanged events only.
That means the code in the closure will only fire if a child is changed.
From the documentation
FIRDataEventTypeChildChanged - Listen for changes to the items in a
list. This event is triggered any time a child node is modified. This
includes any modifications to descendants of the child node. The
snapshot passed to the event listener contains the updated data for
the child.
So that means the code in your closure will only be called when a child is changed, and will not be called when a child is added or deleted.
You need to add additional observers for .childAdded events and .childRemoved events to handle those cases. When you receive a childAdded event, add that snapshot data to your array and likewise when you receive a childRemoved event, remove it from the array.
Remember to reload your table view in all cases so the UI updates with the fresh data.
I personally would change this line to make it more readable
for membersDictionary in membersDictionary {
to
for member in membersDictionary {
but that's just a style thing.
Try using this for reloading tableView
DispatchQueue.main.async {
self.tableView.reloadData()
}
This will help.
you need this :
func tableView(_ tableView: UITableView, commit editingStyle:
UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Deleted")
self.yourArrayName.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
It'll do the job.

The snapshot is not represented correctly

This is the code responsible for uploading text of a post for a blogging app the text of the post is retrieved correctly and saved in snapshot
struct postt {
let username : String!
let textofpost : String!
}
class TableViewController: UITableViewController {
var databaseref = FIRDatabase.database().reference()
var loggedinuser : AnyObject?
var posts = [postt]()
override func viewDidLoad() {
super.viewDidLoad()
self.loggedinuser = FIRAuth.auth()?.currentUser
self.databaseref.child("users").child(self.loggedinuser!.uid).observeSingle
Event(of: .value) {(snapshot:FIRDataSnapshot) in
let snapshot = snapshot.value as! [String:AnyObject]
let username = snapshot["name"] as? String
self.databaseref.child("posts").queryOrderedByKey().observe(.childAdded, with: {( snapshot: FIRDataSnapshot) in
let snapshot = snapshot.value as? NSDictionary
The next variable textofpost doesn't contain anything and i don't know what is the problem so when i represent the cell only the label appears which has a snapshot from the path name in the node users
let textofpost = snapshot?["text"] as? String
self.posts.insert(postt(username : username, textofpost : textofpost), at: 0)
// self.feeds.reloadData()
self.tableView.reloadData()
}
)}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let label = cell.viewWithTag(1) as! UILabel
label.text = posts[indexPath.row].username
let textview = cell.viewWithTag(2) as! UITextView
textview.text = posts[indexPath.row].textofpost
return cell
}
}

Resources