swift xcode didSelectRowAt Indexpath will only select once - ios

I click once and it place a tick , though on the second time clicking it will not remove the tick , same goes for when there is a tick, the first time click the tick will be removed but then once removed and I click again it will not replace the tick?
I can only change the tick to no tick or no tick to tick when i go out of the vc and back in ,
Is there an action/function to call that will reload the ability to action a click with out having to go out of the vc and back in?
Here is my code.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
let key = ref.child("Users").childByAutoId().key
checkIfFollowing(indexPath: indexPath)
var isFollower = false
ref.child("Users").child(uid!).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (ke, value) in following {
if value as? String == self.users[indexPath.row].userID {
isFollower = true
ref.child("Users").child(uid!).child("following/\(ke)").removeValue()
ref.child("Users").child(self.users[indexPath.row].userID!).child("followers/\(ke)").removeValue()
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = .none
//self.checkIfFollowing(indexPath: indexPath)
}
}
}
// if !isFoller - means isFollower is equal to false
if !isFollower {
let following = ["following/\(key)":self.users[indexPath.row].userID]
let followers = ["followers/\(key)" : uid]
ref.child("Users").child(uid!).updateChildValues(following)
ref.child("Users").child(self.users[indexPath.row].userID!).updateChildValues(followers)
//self.checkIfFollowing(indexPath: indexPath)
self.tableView(tableView, cellForRowAt: indexPath).accessoryType = .checkmark
}
})
ref.removeAllObservers()
}
func checkIfFollowing(indexPath: IndexPath){
let ref = FIRDatabase.database().reference()
let uid = FIRAuth.auth()?.currentUser?.uid
ref.child("Users").child(uid!).child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { snapshot in
if let following = snapshot.value as? [String : AnyObject] {
for (_, value) in following {
if value as? String == self.users[indexPath.row].userID {
self.tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
}
}
})
ref.removeAllObservers()
}

Related

Firebase Realtime database - tableview update all the cell data if new data added

I am working on a group chat app and trying to fetch the last message from a node to display the last message under the group name. Everything is working fine. When the group receives a new message, the last message is showing in the correct group and some other random groups as well. If I open the correct group and come back, all the other groups are showing the correct last message. Kindly help me with the below code.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groupList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:GroupsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "GroupsTableViewCell") as! GroupsTableViewCell
cell.selectionStyle = .none
var user: Groupslist
if groupList.count > 0 {
user = groupList[indexPath.row]
cell.lbl_name_group.text = user.name;
cell.view_count.isHidden = true
GetLastMsg(groupID: user.group_id ?? "", cell: cell)
cell.img_group.image = UIImage.init(named:"final_grp")
GetUnReadMsgCount(groupID: user.group_id ?? "", cell: cell)
}
return cell
}
groupList contains the list of groups which is fetched from firebase on viewWillAppear.
func GetLastMsg(groupID : String, cell : GroupsTableViewCell){
let userRef2 = rootRef2.child("message").child(groupID).queryLimited(toLast: 1)
userRef2.observe( .value, with: { snapshot in
guard let _ = snapshot.value as? [String:AnyObject] else {
print("Error")
return
}
print("children count: \(snapshot.children.allObjects.count)")
for snap in snapshot.children.allObjects as! [DataSnapshot] {
let message = Message()
let value = snap.value as? [String: Any] ?? [:]
let text = value["text"] as? String ?? "Name not found"
let mimType = value["mimType"] as? String
let name_doc = value["name_doc"] as? String ?? "file.file"
message.text = text
message.mimType = mimType
message.name_doc = name_doc
message.timestamp_group = (value["timestamp"] as! NSNumber)
print(value["idSender"] as? String)
print(message.name_doc)
var msg_last = ""
// cell.lbl_lastmsg.text = msg_last
if message.mimType == "image/jpeg" {
msg_last = "Image"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "im")
}else if message.mimType == "audio/3gpp" {
msg_last = "Audio"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "mi")
}else if message.mimType == "video/mp4" {
msg_last = "Video"
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "vi")
}else if message.mimType == "application/pdf" {
let name_docs = message.name_doc!.split{$0 == "."}.map(String.init)
msg_last = name_docs.last!
cell.img_small_width.constant=28
cell.img_small.image = UIImage(named: "doc")
}else if mimType == nil{
msg_last = message.text!
cell.img_small_width.constant=0
}
DispatchQueue.main.async {
cell.lbl_lastmsg.text = msg_last
}
}
}
})
}
New method:
func GetUnReadMsgCount(groupID : String, cell : GroupsTableViewCell) {
rootRef2.child("group_message_unread").child(current_user!).child(groupID).observe(.childAdded, with: { (snapshot) -> Void in
print(snapshot.childrenCount)
print(snapshot.children.description)
if snapshot.childrenCount > 0 {
DispatchQueue.main.async {
cell.view_count.isHidden = false
cell.lbl_count.text = String(snapshot.childrenCount)
// self.tbl_groups.reloadRows(at: [IndexPath(row: 3, section: 0)], with: .fade)
// self.tbl_groups.reloadData()
}
}
})
}
That's the expected behavior. In a .observe( .value listener, the snapshot is always the complete data at the path. So when you add a child node, the snapshot fires with the entire new data at userRef2.
You have two main options to not get duplicates in your UI:
Clear all messages when there is an update, before adding the data to the table again. So that'd be right before for snap in snapshot.children in your code. This is by far the simplest way to solve the problem if duplicate rows, but may lead to some flashing in your UI.
Listen for child events, instead of observing the entire value. With this you'll get a single .childAdded event for the new child, you can get rid of the for snap in snapshot.children.allObjects and just add the singular new child node to the table.

Strange behavior in table view after deleting event

I am working on an app which has users register for volunteering events, and am currently working on letting them view their own events and unregister.
This is the screen the users sees when the ask to view their events, in this case the two events are called "ok" and "that" (I just created some random testing events). When you click on one this is the screen you get:
When you click unregister, the event is deleted from your registeredEvents, but the table view now has two of the same events.
When I click back, and then go back to the table view, everything is normal, in this case only displaying the "ok" event, because the other one was deleted. Here is the code for the tableview layout:
override func viewDidLoad() {
super.viewDidLoad()
yourArray = []
actualEvents = []
let id = Auth.auth().currentUser?.uid
Database.database().reference().child("users").child(id!).child("registeredEvents").observe(.value) { snapshot in
let children = snapshot.children
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
print(value)
self.yourArray.append(value as! String)
}
Database.database().reference().child("Events").observe(.value) { (data) in
let events = data.value as! [String:[String:Any]]
for(_,value) in events{
if(self.yourArray.contains(value["EventName"]! as! String)){
self.actualEvents.append(PersonalEvents(evName: value["EventName"]! as! String, evDesc: value["EventDescription"]! as! String, evStartDate: value["start time"]! as! String, evEndDate: value["end time"] as! String, evNumPeople: value["NumberOfPeople"]! as! Int, evNumRegistered: value["currentPeople"] as! Int))
}
}
print("Actual events array " + "\(self.actualEvents)")
}
self.tblEvents.reloadData()
}
print(yourArray)
self.tblEvents.dataSource = self
self.tblEvents.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "productstable", for: indexPath)
cell.textLabel?.text = self.yourArray[indexPath.row]
return cell
}
And the unregister button(This is in a different view controller, becuase info is displayed about the events in a different view):
#IBAction func didUnregister(_ sender: Any) {
let id = Auth.auth().currentUser?.uid
let ref = Database.database().reference()
ref.child("users").child(id!).child("registeredEvents").child(personalEventInfo!.eventName!).removeValue { error,arg in
if error != nil {
print("error \(error)")
}
}
let event = ref.child("Events")
event.child(personalEventInfo!.eventName!).observeSingleEvent(of: .value) { (snapshot) in
let value = snapshot.value as! NSDictionary
var currentPeople = value["currentPeople"] as! Int
currentPeople = currentPeople - 1
Database.database().reference().child("Events").child(self.personalEventInfo!.eventName!).child("currentPeople").setValue(currentPeople)
}
}
Please let me know if this is confusing, but if it is not, then please let me know why this is happening, and what I can do to fix it.
Remove the items inside the Array before you append the data inside.
Improvement: You need to take care your memory by using [weak self] inside these closures.
Can you try the following:
Database.database().reference().child("users").child(id!).child("registeredEvents").observe(.value) { [weak self] snapshot in
let children = snapshot.children
self?.yourArray.removeAll()
while let rest = children.nextObject() as? DataSnapshot, let value = rest.value {
print(value)
self?.yourArray.append(value as! String)
}
Database.database().reference().child("Events").observe(.value) { [weak self] (data) in
let events = data.value as! [String:[String:Any]]
self?.actualEvents.removeAll()
for(_,value) in events{
if(self?.yourArray.contains(value["EventName"]! as! String)){
self?.actualEvents.append(PersonalEvents(evName: value["EventName"]! as! String, evDesc: value["EventDescription"]! as! String, evStartDate: value["start time"]! as! String, evEndDate: value["end time"] as! String, evNumPeople: value["NumberOfPeople"]! as! Int, evNumRegistered: value["currentPeople"] as! Int))
}
}
print("Actual events array " + "\(self?.actualEvents)")
}
self?.tblEvents.reloadData()
}

How can prepareForReuse() be implemented to prevent a faded cell from acting weird?

I use self.like.alpha = 0.5 to highlight the . like button next to the user who was liked. Scrolling causes the highlight to sometimes disappear and appear next to other users.
I have figured out that I can either use A) prepareForReuse or B) do something like this pseudocode (although IsLiked would have to be confined to that session somehow) :
cell.like.alpha = item.isLiked ? 0.5 : 1
Here is my code:
In View ControllerTableViewCell.swift:
#IBAction func likePressed(_ sender: Any) {
self.like.alpha = 0.5
let ref = Database.database().reference()
let keyToPost = ref.child("likes").childByAutoId().key
ref.child("humans").child(self.postID).observeSingleEvent(of: .value, with: {(snapshot) in
if let humans = snapshot.value as? [String: AnyObject] {
let updateLikes: [String: Any] = ["humansWhoLike/\(keyToPost)" : Auth.auth().currentUser!.uid]
ref.child("humans").child(self.postID).updateChildValues(updateLikes, withCompletionBlock: { (error, reff) in
if error == nil {
ref.child("humans").child(self.postID).observeSingleEvent(of: .value, with: { (snap) in
if let properties = snap.value as?[String: AnyObject]{
if let likes = properties["humansWhoLike"] as? [String : AnyObject] {
let count = likes.count
let update = ["likes" : count]
ref.child("humans").child(self.postID).updateChildValues(update)
}
}
})
}
})
}
})
ref.removeAllObservers()
}
In homepage.swift(where like button is used):
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let like = cell.viewWithTag(3) as! UIButton
let immy = cell.viewWithTag(1) as! UIImageView
let person: Userx = humans[indexPath.row]
cell.lblName.text = person.Education
cell.postID = self.humans[indexPath.row].postID
if let PhotoPosts = person.PhotoPosts {
let url = URL(string: PhotoPosts)
immy.sd_setImage(with: url)
}
return cell
}
I need the like fade to not go away and not go to other users when cells get reused after scrolling.
Database Structure:
"humans" : {
"5VbL3Adj7teM2KNJkF4GIexBJhE2" : {
"Coordinates" : {
"latitude" : 42.8864,
"longitude" : 78.8784
},
"Education" : "Yale Accounting",
"PhotoPosts" : "https://firebasestorage.googleapis.com/v0/b/daylike-2f938.appspot.com/o/images%2FPhotoPosts?alt=media&token=42d76567-ac42-4728-9914-1d7c2fa4d5e6",
"WhatIamConsideringBuying" : "Twitter: Carla9",
"caption" : 1565896677651,
"caption1" : {
"caption1" : 1566734644170,
"keyToPost" : "-Ln7eM9S_pSQ_aqRPz0k"
},
"likes" : 82,
"peopleWhoLike" : {
"-LmLjHwwGj1kt5qLM20X" : "NMNQYJ5z64fATK2MMs7m0ggHl0k2",
"-LmLtlp5Sm900SV8xP4i" : "NMNQYJ5z64fATK2MMs7m0ggHl0k2",
"-LmLuOzQ0TZ9uJ_uNkkg" : "NMNQYJ5z64fATK2MMs7m0ggHl0k2",
"-LmLultcrrsG2NjEYoTe" : "NMNQYJ5z64fATK2MMs7m0ggHl0k2",
"-LmLvMseSmFhnxEGGctU" : "NMNQYJ5z64fATK2MMs7m0ggHl0k2",
"-LmLvVQokDCZLFrnhaLu" : "NMNQYJ5z64fATK2MMs7m0ggHl0k2",
},
"postID" : "5VbL3Adj7teM2KNJkF4GIexBJhE2",
"users" : "carla19martin#me.com"
},
"9RYUttVhCzXKs6H1XnZ63TZ8Dun2" : {
You need to set the alpha for each cell in cellForRow method. You should not change it based on the cell itself sending an action to your likePressed() method. Instead, it should reflect your underlying data model, which I assume you have cached locally.
public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! ViewControllerTableViewCell
let like = cell.viewWithTag(3) as! UIButton
let immy = cell.viewWithTag(1) as! UIImageView
let person: Userx = humans[indexPath.row]
cell.lblName.text = person.Education
cell.postID = self.humans[indexPath.row].postID
if let PhotoPosts = person.PhotoPosts {
let url = URL(string: PhotoPosts)
immy.sd_setImage(with: url)
}
// You need to set the alpha here in cellForRow method
// Your idea using nil coalescing is good, as it must always be set afresh for the relevant cell (so it is correct when reused)
// Get value of isLiked - this depends on your database and local cache
// This is an example - accessing the isLiked property may be different depending on your data model
cell.like.alpha = posts[indexPath.row].isLiked ? 0.5 : 1
return cell
}

Index out of range for only last 2 values?

I am currently getting index out of range but this only happens after the first 3 items have been displayed successfully. I looked through the code and I still don't know where im going wrong. Index out of range happens only when trying to load text in the cells.
Log when method is called from viewdidload:
Description of indexPath: 0
Description of indexPath: 1
Description of indexPath: 2
Log when I press a button to load the rest of images:
Description of indexPath: 0
Description of indexPath: 1
Description of indexPath: 2
Description of indexPath: 3
Datasource method where indexpath is out of range:
//MARK: - tableview datasource methods
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if arrayOfImgObj.count > 0 {
cell.imageView?.image = arrayOfImgObj[indexPath.row].imageTempt
print("Description of indexPath: ",indexPath.row)
cell.textLabel?.text = arrayOfUIDs[indexPath.row] // index out of range
} else {
print("\(arrayOfImgObj.count) not over 0 yet")
}
return cell
}
Method that is called when viewDidload:
//testing array
var arrayOfUIDs = User().users
func fetchAllUserFristImage() {
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if (snapshot.value as? [String: AnyObject]) != nil {
let user = User()
user.id = snapshot.key
//testing array
self.arrayOfUIDs.append(snapshot.key)
print("\(String(describing: user.id)) <-- SHUD BE THE USERUID")
self.databaseRef = Database.database().reference()
let usersPostRef2 = self.databaseRef.child("Posts").child(user.id!)
usersPostRef2.observe(.value, with: {(postXSnapshots) in
if let postDictionary2 = postXSnapshots.value as? NSDictionary {
for (p) in postDictionary2 {
let posts = p.value as! NSDictionary
//to get back to where i was delete the below for i
for (i) in posts {
let imageUrlString = i.value as! NSDictionary
guard let postUrl = imageUrlString.value(forKey: "image1") else {return}
//below is ting from below
if postUrl != nil {
self.feedArray.append(Post(fetchedImageURL: postUrl as! String))
let imageUrlString = "\(postUrl)"
let imageUrl = URL(string: imageUrlString)!
print("\(imageUrl) this shud be img url's of posts")
let imageDataL = try! Data(contentsOf: imageUrl)
self.imageObject.img2 = UIImage(data: imageDataL)
let image1ToDisplay: UIImage = self.imageObject.img2!
self.arrayOfImgObj.append(forTable(imageTempt: image1ToDisplay))
} else {print("this user had no posts, was nil")}
}
}
}
})
}
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
Edit:
I have changed teh code to only use one array of objects:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if arrayOfImgObj.count > 0 {
cell.imageView?.image = arrayOfImgObj[indexPath.row].imageTempt
print("Description of indexPath: ",indexPath.row)
cell.textLabel?.text = arrayOfImgObj[indexPath.row].users[indexPath.row] // index out of range
} else {
print("\(arrayOfImgObj.count) not over 0 yet")
}
return cell
}
changing one line in the function to this:
self.arrayOfImgObj.append(forTable(imageTempt: image1ToDisplay, users: snapshot.key))
Thanks in advance!
Your problem is that you have two arrays:
arrayOfUIDs which has one entry for each user
arrayOfImgObj which has 0-n entries for each user (So, it is likely that the number of objects in this array will be greater than the number of objects in arrayOfUIDs.
You are basing your row count on the arrayOfImgObj, but then using the row number to index into arrayOfUIDs, which results in an array bounds exception.
It may well be more elegant to have table view section per userid, but if you want to put all of the rows in a single section I would suggest you use a single array of structs as your data model.
Use something like:
struct UserImage {
var userID: String
var image: UIImage
}
var tableData = [UserImage]()
Then as you fetch each image, create a new struct and put it in your array:
func fetchAllUserFristImage() {
Database.database().reference().child("Posts").observe(.childAdded, with: {(snapshot) in
if snapshot.value as? [String: AnyObject] != nil {
let user = snapshot.key
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] {
//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 {
self.feedArray.append(Post(fetchedImageURL: postUrl))
if let imageUrl = URL(string: postUrl), let imageDataL = try? Data(contentsOf: imageUrl), let image = UIImage(data: imageDataL) {
self.tableData.append(UserImage(userID: user, image: image))
} else {print("this user had no posts, was nil")}
}
}
}
}
}
})
//below shud stay same
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
}
Now, your cellForRow can be:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.imageView?.image = tableData[indexPath.row].image
cell.textLabel?.text = tableData[indexPath.row].userID
return cell
}

UITableView reloadData() using lots of power calling from cellForRowAt

My UITableView will only show all of the fully loaded contents in the cells if I follow the advice of this post and call reloadData() in the main thread with DispatchQueue. I'm doing this in tableView cellForRowAt because it wasn't fully loading the values from my array into all of the cells otherwise. Now it's constantly reloading and is obviously very energy intensive. This makes it harder to scroll in the Table View, which makes sense.
How can I prevent this ridiculous use of energy while still loading the data into the cells as I need?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell? =
charitiesSupportedTable?.dequeueReusableCell(withIdentifier: "SupportedCell")
if (cell == nil)
{
cell = UITableViewCell(style: UITableViewCellStyle.subtitle,
reuseIdentifier: "abc")
}
if(indexPath.row <= moneyRaisedOfPrevPostsArray.count-1){
cell?.textLabel?.text = charOfPrevPostsArray[indexPath.row]
cell?.detailTextLabel?.text = moneyRaisedOfPrevPostsArray[indexPath.row]
}
DispatchQueue.main.async {
self.charitiesSupportedTable.reloadData()
}
return cell!
}
Here's my viewDidLoad where I create the array
override func viewDidLoad() {
super.viewDidLoad()
let userEmail = Auth.auth().currentUser!.email
var email1 = userEmail?.replacingOccurrences(of: "#", with: "", options: NSString.CompareOptions.literal, range:nil)
email1 = email1?.replacingOccurrences(of: ".", with: "", options: NSString.CompareOptions.literal, range:nil)
charitiesSupportedTable.delegate = self
charitiesSupportedTable.dataSource = self
charitiesSupportedTable.isScrollEnabled = false
reff = Database.database().reference().child("Posts")
let userRef1 = reff?.queryOrdered(byChild: "user").queryEqual(toValue : email1)
userRef1?.observe(.value, with: { (snapshot) in
for child in snapshot.children {
let snap = child as! DataSnapshot
let key = snap.key
let value = snap.value
let valueAsDict = value as! NSDictionary
charityOfPrevPosts = valueAsDict.value(forKey: "charity") as! String
viewCountOfPrevPosts = valueAsDict.value(forKey: "viewCount") as! String // times .006
moneyRaisedPrevPosts = Double(viewCountOfPrevPosts)! * 0.006
charOfPrevPostsArray.append(charityOfPrevPosts)
moneyRaisedOfPrevPostsArray.append(String(moneyRaisedPrevPosts))
}
charOfPrevPostsArray.reverse()
moneyRaisedOfPrevPostsArray.reverse()
}
)
moneyRaisedOfPrevPostsArray.removeAll()
charOfPrevPostsArray.removeAll()
reff = Database.database().reference().child("UsersViews").child(email1!).child("totalMoneyRaised")
reff?.observe(DataEventType.value, with: { (snapshot) in
var totalMoney = snapshot.value as! Double
self.totalMoneyLabel.text = String(format: "$%.02f", totalMoney)
})
// don't know if this part is relevant but I do return charitiesCount + 2 in numberOfRowsInSection
reff = Database.database().reference()
databaseHandle = reff?.child("UsersViews").child(email1!).child("charitiesSupported").observe(.value, with: { (snapshot) in
let myDictionary11 = snapshot.value as? NSDictionary
// Adds brands and charities to myData array
for value in (myDictionary11?.allValues)! {
self.myCharities.append(value as! String)
}
charitiesCount = self.myCharities.count
})
}

Resources