TableView stop reuse - ios

I am trying to display users for a messaging app in a TableView however when I scroll it updates the cells and therefore pulls the data again thus lagging. I am using a prototype cell in a storyboard.
import UIKit
import FirebaseAuth
import FirebaseDatabase
class TableViewCell: UITableViewCell {
#IBOutlet weak var profilePicture: UIImageView!
#IBOutlet weak var statusImage: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
override func prepareForReuse() -> Void {
profilePicture.image = nil
statusImage.backgroundColor = UIColor.systemGreen
nameLabel.text = nil
}
}
class MainController: UITableViewController {
#IBOutlet weak var tableViews: UITableView!
var users = [String]()
var decryptedUsers = [String]()
#IBAction func signOut(_ sender: Any) {
do {
let userid = Auth.auth().currentUser?.uid
let dbReference = Database.database().reference().child("Users").child(userid!).child("status")
dbReference.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { snapshot,error in
if (snapshot.value as? String == "online") {
dbReference.setValue("offline")
}
})
try Auth.auth().signOut()
self.performSegue(withIdentifier: "backToSignIn", sender: nil)
} catch {
let alert = UIAlertController(title: "Error", message: "There was an error signing out. Please ensure that you are connected to the internet.", preferredStyle: UIAlertController.Style.alert)
let okButton = UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)
alert.addAction(okButton)
self.present(alert, animated: true, completion: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
setOnline()
fetchUsers()
}
static var userslist = [String]()
func fetchUsers() {
let userid = (Auth.auth().currentUser?.uid)!
let dbRef = Database.database().reference().child("Chatlist").child(userid)
dbRef.observe(.value) { (snapshot, error) in
self.users.removeAll()
for yeet in snapshot.children {
let yeetsnap = yeet as! DataSnapshot
self.users.append(yeetsnap.childSnapshot(forPath: "id").value as! String)
print(self.users)
self.tableViews.reloadData()
}
}
}
func setOnline() {
let userid = Auth.auth().currentUser?.uid
let dbReference = Database.database().reference().child("Users").child(userid!).child("status")
dbReference.observeSingleEvent(of: .value, andPreviousSiblingKeyWith: { snapshot,error in
if (snapshot.value as? String == "offline") {
dbReference.setValue("online")
}
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: TableViewCell?
cell = nil
if (cell == nil) {
cell = (tableView.dequeueReusableCell(withIdentifier: "CellProto") as? TableViewCell)!
DispatchQueue.main.async {
cell!.profilePicture.layer.masksToBounds = true
cell!.profilePicture.layer.cornerRadius = (cell!.profilePicture.bounds.width) / 2
cell!.statusImage.layer.masksToBounds = true
cell!.statusImage.layer.cornerRadius = (cell!.statusImage.bounds.width) / 2
let dbRef = Database.database().reference().child("Users").child(self.users[indexPath.row])
dbRef.observeSingleEvent(of: .value) { (snapshot) in
cell!.nameLabel?.text = snapshot.childSnapshot(forPath: "username").value as? String
let urlstring = snapshot.childSnapshot(forPath: "imageURL").value as? String
if (urlstring != "default") {
let url = URL(string: urlstring!)
let data = try? Data(contentsOf: url!)
cell!.profilePicture.image = UIImage(data: data!)
}
let statusstring = snapshot.childSnapshot(forPath: "status").value as? String
if (statusstring == "online") {
cell!.statusImage.backgroundColor = UIColor.systemGreen
} else if (statusstring == "offline") {
cell!.statusImage.backgroundColor = UIColor.systemGray
} else if (statusstring == "appearoffline") {
cell!.statusImage.backgroundColor = UIColor.systemGray
} else if (statusstring == "dnd") {
cell!.statusImage.backgroundColor = UIColor.systemRed
}
}
}
}
print("test")
return cell!
}
}

Do not load the data every time within the cellForRow. This reloads the data every time you scroll, causing the table to lag.
A better approach would be to load the data once by putting the database retrieving code in here:
func viewWillAppear(_ animated: Bool){
super.viewWillAppear(animated)
// retrieve the data here by calling a separate function
}
Whatever you do, it is not a good coding approach to load data within the cellForRow method.
You can also retrieve the data by calling the function whenever the user swipes down from the top of the table to reload the table.
One more option would be to only load a number of rows at once (10 max for example), then when the user scroll to the bottom of the 10 rows and keeps scrolling it will then retrieve the next 10 datapoints for the next 10 rows in the table.

Related

Programmatically set the row height for a tableview controller with varying image aspect ratios?

Currently I'm working on a media feed that may contain images in varying aspect ratios. Each image is to be scaled to the same width on the screen and the height will be adjusted according to the same ratio.
My tableViewController is using automatic dimensions. Each image is scaled to the correct width and height then a new image is created which is fed into its corresponding tableViewCell.
However, when the width and height is set programmatically (via auto dimension and the scaled image), I find a strange bug where I have to scroll around to get the image to display. (Video below). It's not until I use heightForRowAt and use a constant value where the aspect ratio is not preserved that the images display at the apps launch.
I'd love to be able to display these images in their correct aspect ratio. Any help is definitely appreciated.
Thanks!
First Image: auto dimension
Images display after scrolling action (maybe a bug)
Second Image: height for row at
TableVC cell class
import UIKit
import Firebase
class UserPostCell: UITableViewCell {
// MARK: - Outlets
#IBOutlet private weak var userProfileBtn: UIButton!
#IBOutlet private weak var likeBtn: UIButton!
#IBOutlet private weak var captionTxtField: UITextField!
#IBOutlet weak var postImage: UIImageView!
private(set) var height: CGFloat?
override func awakeFromNib() {
super.awakeFromNib()
}
func configureCell(post: UserPost) {
let gsReference = Storage.storage().reference(forURL: post.photoURL)
var image: UIImage?
gsReference.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
debugPrint("Error: \(error.localizedDescription)")
} else {
image = UIImage(data: data!)
//let h = image!.size.height
let w = image!.size.width
let wRatio = self.frame.size.width / w
//create new image at correct scale
let newImage = UIImage(data: data!, scale: 1 / wRatio)
self.postImage.frame.size.height = newImage!.size.height
self.postImage.image = newImage
self.userProfileBtn.setTitle(post.username, for: .normal)
self.captionTxtField.text = post.caption
}
}
}
}
View Controller
import UIKit
import Firebase
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
// MARK: - Outlets
#IBOutlet private weak var tableView: UITableView!
// MARK: - Variables
private var userPosts = [UserPost]()
private var postsCollectionRef: CollectionReference!
private var usersCollectionRef: CollectionReference!
private var handle: AuthStateDidChangeListenerHandle?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 450
postsCollectionRef = Firestore.firestore().collection(POSTS_REF)
usersCollectionRef = Firestore.firestore().collection(USERS_REF)
loadPosts()
}
func loadPosts() {
var username: String?
var profileURL: String?
//var photoURL: String?
var uid: String?
//retrieve posts from database
postsCollectionRef.getDocuments { snapshot, error in
if let err = error {
debugPrint(err.localizedDescription)
} else {
guard let snap = snapshot else { return }
for d in snap.documents {
let data = d.data()
uid = data[USER_ID] as? String ?? ""
//retrieve info about author of each post
let userDocRef = self.usersCollectionRef.document(uid!)
//retrieve user info for each post
userDocRef.getDocument { document, error in
if let document = document, document.exists {
let dat = document.data()
//user data (dat)
username = dat![USERNAME] as? String ?? "Anonymous"
profileURL = dat![PROFILE_IMAGE] as? String ?? ""
// dat vs data fix this ^^^
//post data (data)
let photoURL = data[PHOTO_URL] as? String ?? ""
let caption = data[CAPTION] as? String ?? ""
let numComments = data[NUM_COMMENTS] as? Int ?? 0
let numLikes = data[NUM_LIKES] as? Int ?? 0
let timestamp = data[TIME_STAMP] as? Date ?? Date()
let documentId = d.documentID
print("photolink: \(photoURL)")
print("caption: \(caption)")
let newPost = UserPost(name: username!, timestamp: timestamp, caption: caption, numLikes: numLikes, numComments: numComments, documentId: documentId, UID: uid!, profile: profileURL!, photo: photoURL)
self.userPosts.append(newPost)
self.tableView.reloadData()
} else {
print("This user document does not exist")
}
}
}
}
}
}
override func viewWillAppear(_ animated: Bool) {
//check if the user is logged in or not
handle = Auth.auth().addStateDidChangeListener({ (auth, user) in
if user == nil {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let loginVC = storyboard.instantiateViewController(identifier: "loginVC")
self.present(loginVC, animated: true, completion: nil)
} else {
//self.loadPosts()
// set listener
self.tableView.reloadData()
}
})
}
#IBAction func logoutBtnTapped(_ sender: Any) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
} catch let signoutError as NSError {
debugPrint("Error signing out: \(signoutError)")
}
}
#IBAction func reload(_ sender: Any) {
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userPosts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "userPostCell", for: indexPath) as? UserPostCell{
cell.configureCell(post: userPosts[indexPath.row])
return cell
} else {
return UITableViewCell()
}
}
}
The reason of this your cell is already created before UIImage loads up.
Best way to prevent this, give one of the constraints of the imageView.

tableView in XCode is returning cells with random data from array

I am making a simple messaging app in xcode. The table view is populated with custom cells that contain a right and left view. When the user receives a message the left view is populated and when the user sends a message the right view is populated.
The PROBLEM is after reloading the tableView sometimes I will see cells filled with scrambled data from the array containing the messages. I'm not sure what is happening.
Things I've tried:
I've checked the message files that are stored on my backend in Back4App and they all contain the corrent csv data
I looked at my cell count which is always accurate
I've printed the array and cell data before the table reloads and after it reloads and the data in the array's is all correct.
If I leave the message VC and return to it all the messages are displayed correctly but when I stay in the VC and use the send button to send messages the data gets scrambled a little and prints weird combinations of my tableView
You can see the bottom line is not displayed correctly:
Image showing the error
Here is my chat VC:
//
// ChatViewController.swift
// Glam
//
// Created by Student on 11/14/20.
// Copyright © 2020 Tucker Weibell. All rights reserved.
//
import UIKit
import Parse
class ChatViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var navTitle: UINavigationItem!
var senders = [String]()
var message = [String]()
var state = [String]()
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var myView: UIView!
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
var dataStringSender = ""
var dataStringReciever = ""
var senderName = ""
override func viewDidLoad() {
super.viewDidLoad()
getData()
navTitle.title = MessageCustomerViewController.GlobalVars.selectedItem
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: UIResponder.keyboardWillShowNotification, object: nil)
let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
self.view.addGestureRecognizer(tap)
}
func getData() {
let user = PFUser.current()
let username = user?.username
senderName = username!
let query = PFUser.query()
query?.whereKey("username", equalTo: username!)
do {
let result = try query?.findObjects()
let file = result![0]["Messages"] as! PFFileObject
do {
let data = try file.getData()
let string = String(decoding: data, as: UTF8.self)
dataStringSender = string
let cvsRows = string.components(separatedBy: "\n")
for row in cvsRows {
var itemsRow = row.components(separatedBy: ",")
print(itemsRow)
itemsRow[2] = itemsRow[2].replacingOccurrences(of: "\r", with: "")
print(itemsRow)
if itemsRow[0] == MessageCustomerViewController.GlobalVars.selectedItem {
senders.append(itemsRow[0])
message.append(itemsRow[1])
state.append(itemsRow[2])
}
}
}
catch {
print(error.localizedDescription)
}
}
catch {
print(error.localizedDescription)
}
loadData()
}
#IBAction func sendMessage(_ sender: Any) {
let query = PFUser.query()
query?.whereKey("username", equalTo: MessageCustomerViewController.GlobalVars.selectedItem)
do {
let result = try query?.findObjects()
if result?[0]["Messages"] != nil {
let file = result![0]["Messages"] as! PFFileObject
do {
let data = try file.getData()
let string = String(decoding: data, as: UTF8.self)
dataStringReciever = string
}
catch {
print(error)
}
}
}
catch {
print(error)
}
dataStringSender = dataStringSender + "\n" + MessageCustomerViewController.GlobalVars.selectedItem + "," + textView.text + "," + "Sent"
dataStringReciever = dataStringReciever + "\n" + senderName + "," + textView.text + "," + "Recieved"
let dataSent = Data(dataStringSender.utf8)
let dataRecieved = Data(dataStringReciever.utf8)
//let fileSent: PFFileObject = PFFileObject(data: dataSent)!
//let fileRecieved: PFFileObject = PFFileObject(data: dataRecieved)!
let fileSent = PFFileObject(name: "message.csv", data: dataSent)
let fileRecieved = PFFileObject(name: "message.csv", data: dataRecieved)
let user = PFUser.current()
user!["Messages"] = fileSent
user?.saveInBackground()
let newQuery = PFUser.query()
newQuery?.whereKey("username", equalTo: MessageCustomerViewController.GlobalVars.selectedItem)
do {
let newResults = try newQuery?.findObjects()
newResults![0]["Messages"] = fileRecieved
newResults![0].saveInBackground()
}
catch {
print(error)
}
clearData()
getData()
print("\n")
print("\n")
print(message)
print("\n")
print("\n")
print(state)
loadData()
}
func loadData() {
self.tableView.reloadData()
}
func clearData() {
message.removeAll()
state.removeAll()
senders.removeAll()
}
#objc func handleKeyboardNotification(notification: NSNotification) {
if let keyboardFrame: NSValue = notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
var height = keyboardRectangle.height
height = height * -1
bottomConstraint.constant = height + 85
}
}
#objc func dismissKeyboard(sender: UITapGestureRecognizer) {
bottomConstraint.constant = 0
textView.resignFirstResponder()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return message.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print([message[indexPath.row]])
let cell = tableView.dequeueReusableCell(withIdentifier: "messagescell") as! MessagesCell
if state[indexPath.row] == "Sent" {
cell.sentText.text = message[indexPath.row]
cell.sentView.backgroundColor = .clear
}
else {
cell.recievedText.text = message[indexPath.row]
cell.recievedView.backgroundColor = .clear
}
return cell
}
}
That sounds a lot like an reusable cell issue.
Since you are reusing your cells here let cell = tableView.dequeueReusableCell(withIdentifier: "messagescell") as! MessagesCell the properties of those cells happen to have old states from time to time if you not explicitly set new values to all of them.
You should override prepareForReuse() in your custom cell implementation and reset your cell to default values.

iOS / Swift - Appending a filter to retrieving data from Firebase

What I got so far is a tableView and custom Cells about hookah tobacco. Those include an image, name, brand and ID. Now what I try to reach is basically a tableview that contains only the cells with attributes based on a "filter". For example the tableView that appears at the beginning has only the following two settings to make it simple: PriceRange and BrandName. At the first time loading the tableView those are PriceRange: 0 - 100 and Brands: all brands. Then imagine a user restricting those like 0 - 15 Euros and only brand called "7 Days". How exactly would I do that with reloading the tableView?
import UIKit
import Firebase
class ShopViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var button_filter: UIBarButtonItem!
#IBOutlet weak var searchBar_shop: UISearchBar!
#IBOutlet weak var view_navigator: UIView!
#IBOutlet weak var tableView_shop: UITableView!
var ShopCells: [ShopCell] = []
var databaseRef: DatabaseReference!
var storageRef: StorageReference!
override func viewDidLoad() {
super.viewDidLoad()
self.databaseRef = Database.database().reference()
self.storageRef = Storage.storage().reference()
createArray() { shopCells in
for item in shopCells {
self.ShopCells.append(item)
}
DispatchQueue.main.async {
self.tableView_shop.reloadData()
}
}
self.navigationItem.title = "Shop"
self.tableView_shop.delegate = self
self.tableView_shop.dataSource = self
self.searchBar_shop.delegate = self
self.searchBar_shop.barTintColor = UIColor(hexString: "#1ABC9C")
self.view_navigator.backgroundColor = UIColor(hexString: "#1ABC9C")
self.tableView_shop.separatorColor = UIColor.clear
self.searchBar_shop.isTranslucent = false
self.searchBar_shop.backgroundImage = UIImage()
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(ShopViewController.viewTapped(gestureRecognizer:)))
view.addGestureRecognizer(tapGesture)
}
#objc func viewTapped(gestureRecognizer: UITapGestureRecognizer) {
view.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
self.searchBar_shop.resignFirstResponder()
}
func createArray(completion: #escaping ([ShopCell]) -> () ) {
var tempShopCells: [ShopCell] = []
let rootRef = Database.database().reference()
let query = rootRef.child("tobaccos").queryOrdered(byChild: "name")
query.observeSingleEvent(of: .value) { (snapshot) in
let dispatchGroup = DispatchGroup()
for child in snapshot.children.allObjects as! [DataSnapshot] {
let value = child.value as? [String: Any];
let name = value?["name"] as? String ?? "";
let brand = value?["brand"] as? String ?? "";
let iD = value?["iD"] as? String ?? "";
dispatchGroup.enter()
let imageReference = Storage.storage().reference().child("tobaccoPictures").child("\(iD).jpg")
imageReference.getData(maxSize: (1 * 1024 * 1024)) { (data, error) in
if let _error = error{
print(_error)
} else {
if let _data = data {
let image: UIImage! = UIImage(data: _data)
tempShopCells.append(ShopCell(productName: name, brandName: brand, productImage: image, iD: iD))
}
}
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completion(tempShopCells)
}
}
}
}
extension ShopViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.ShopCells.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let shopCell = ShopCells[indexPath.row]
let cell = tableView_shop.dequeueReusableCell(withIdentifier: "ShopCell") as! ShopTableViewCell
cell.setShopCell(shopCell: shopCell)
return cell
}
}

How to pull users from database and list them in a table view using firebase?

I'm using firebase to make an iOS app. I want to retrieve all the users on my database and display their name and profile picture in a table view. Here is my code for my TableViewCell:
import UIKit
import FirebaseDatabase
import FirebaseAuth
import SDWebImage
class HomeTableViewCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var profileImageView: UIImageView!
#IBOutlet weak var likeImageView: UIImageView!
#IBOutlet weak var messageImageView: UIImageView!
#IBOutlet weak var likeCountButton: UIButton!
var homeVC: HomeViewController?
var postReference: DatabaseReference!
var post: UserFile?{
didSet {
updateView()
}
}
var user: UserFile? {
didSet {
updateUserInfo()
}
}
override func awakeFromNib() {
super.awakeFromNib()
nameLabel.text = ""
let berryTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleLikeTap))
likeImageView.addGestureRecognizer(berryTapGesture)
likeImageView.isUserInteractionEnabled = true
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func updateView() {
if let photoURL = post?.picURL {
profileImageView.sd_setImage(with: URL(string: photoURL))
}
API.Post.REF_POSTS.child(post!.id!).observeSingleEvent(of: .value, with: { postSnapshot in
if let postDictionary = postSnapshot.value as? [String:Any] {
let post = UserFile.transformPost(postDictionary: postDictionary, key: postSnapshot.key)
self.updateLike(post: post)
}
})
API.Post.REF_POSTS.child(post!.id!).observe(.childChanged, with: { snapshot in
if let value = snapshot.value as? Int {
self.likeCountButton.setTitle("\(value) berries", for: .normal)
}
})
}
func updateLike(post: UserFile) {
let imageName = post.berries == nil || !post.isBerried! ? "berry" : "berrySelected"
likeImageView.image = UIImage(named: imageName)
// display a message for berries
guard let count = post.berryCount else {
return
}
if count != 0 {
likeCountButton.setTitle("\(count) berries", for: .normal)
} else if post.berryCount == 0 {
likeCountButton.setTitle("Be the first to Like this", for: .normal)
}
}
func incrementberries(forReference ref: DatabaseReference) {
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
if var post = currentData.value as? [String : AnyObject], let uid = Auth.auth().currentUser?.uid {
var berries: Dictionary<String, Bool>
berries = post["berries"] as? [String : Bool] ?? [:]
var likeCount = post["berryCount"] as? Int ?? 0
if let _ = berries[uid] {
// Unlike the post and remove self from stars
likeCount -= 1
berries.removeValue(forKey: uid)
} else {
// Like the post and add self to stars
likeCount += 1
berries[uid] = true
}
post["berryCount"] = likeCount as AnyObject?
post["berries"] = berries as AnyObject?
currentData.value = post
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
}) { (error, committed, snapshot) in
if let error = error {
print(error.localizedDescription)
}
if let postDictionary = snapshot?.value as? [String:Any] {
let post = UserFile.transformPost(postDictionary: postDictionary, key: snapshot!.key)
self.updateLike(post: post)
}
}
}
func handleLikeTap() {
postReference = API.Post.REF_POSTS.child(post!.id!)
incrementberries(forReference: postReference)
}
override func prepareForReuse() {
super.prepareForReuse()
profileImageView.image = UIImage(named: "industribune-default-no-profile-pic")
}
func updateUserInfo() {
nameLabel.text = user?.username
if let photoURL = user?.profileImageURL {
profileImageView.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "industribune-default-no-profile-pic"))
}
}
}
I am displaying this cell on my HomeViewController:
import UIKit
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
import Firebase
class HomeViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
var posts = [UserFile]()
var users = [UserFile]()
override func viewDidLoad() {
super.viewDidLoad()
// for performance set an estimated row height
tableView.estimatedRowHeight = 1
// but also request to dynamically adjust to content using AutoLayout
tableView.rowHeight = UITableViewAutomaticDimension
//tableView.delegate = self
tableView.dataSource = self
loadPosts()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func loadPosts() {
activityIndicatorView.startAnimating()
API.User.observePosts { (newPost) in
guard let userID = newPost.uid else { return }
self.fetchUser(uid: userID, completed: {
// append the new Post and Reload after the user
// has been cached
self.posts.append(newPost)
self.activityIndicatorView.stopAnimating()
self.tableView.reloadData()
})
}
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
API.User.observeUser(withID: uid) { user in
self.users.append(user)
completed()
}
}
}
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
cell.post = posts[indexPath.row]
cell.user = users[indexPath.row]
cell.homeVC = self
return cell
}
}
I have a lot of craziness going on in my project so let me know if you have any questions and what I'm doing wrong. If it's too complicated to understand I'm ready to erase everything and start over too.
And I do honestly think that I followed all the guidelines to ask a question so don't like shut this question down or something.
That's a lot of code. Try this super reduced example. For this, the users node only stores the name as a child node but it could also have an image, email, address, etc.
Example users node
users
uid_0:
name: "Bert"
uid_1:
name: "Ernie"
and some code
var usersArray = [ [String: Any] ]() //an array of dictionaries.
class ViewController: UIViewController {
//set up firebase references here
override func viewDidLoad() {
super.viewDidLoad()
let usersRef = self.ref.child("users")
usersRef.observeSingleEvent(of: .value, with: { snapshot in
for child in snapshot.children {
let snap = child as! DataSnapshot
let userDict = snap.value as! [String: Any]
self.usersArray.append(userDict)
}
self.tableView.reloadData()
})
and the tableView delegate methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.usersArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTableViewCell", for: indexPath) as! HomeTableViewCell
let userDict = self.usersArray[indexPath.row]
cell.text = userDict["name"] as! String
//cell.imge = userDict["image"] etc etc
return cell
}
Now... that all being said. This is the perfect use for an array of UserClass objects instead of the dictionaries.
Here's a starting point....
class UserClass {
var name = ""
var image = ""
func init(snap: DataSnapshot) {
//populate the vars from the snapshot
}
}
var userClassArray = [UserClass]()
Don't copy and paste this as there are probably typos but it should point you in the right direction.

Displaying the count of comments in the feed

I have an Instagram like feed with pictures shown one at a time in each cell. You can press on them and get directed to a commentTBV. Here is where I have all the comments underneath the post and they get counted correctly in this View.
var commentsArray = [FotoComment]()
is the array holding the comments
kommentarArray is the array i want to fill with the assignArray func so i can use it to display the amount of counts.
var kommentarArray = [FotoComment]()
[FotoComment] is my struc I use for my comments
What I want is that already in the feed the commentArray.count will show the correct number of comments.
func assignArray() {
let otherVC = CommentTableViewController()
kommentarArray = otherVC.commentsArray
print(kommentarArray.count)
}
This way I get access from my feed to the array of comments in the CommentTBVC.
My cell is:
cell.kommentarZähler.text = "Kommentare: \(kommentarArray.count)"
But it always shows 0 even though it already has 5 comments and it correctly displayed on the CommentTBV.
M complete code for MemesTableViewConbtroller (the feed)
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
class MemesTableViewController: UITableViewController {
var kommentarArray = [FotoComment]()
var dataBaseRef : FIRDatabaseReference!
var storageRef : FIRStorageReference!
var posts = [PostMitBild]()
var segmentedControl : HMSegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
assignArray()
segmentedControl = HMSegmentedControl(sectionTitles: ["Top Heute", "Beliebteste", "Neue"])
segmentedControl.frame = CGRect(x: 10, y: 10, width: 300, height: 60)
segmentedControl.backgroundColor = UIColor.red
segmentedControl.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
segmentedControl.borderColor = UIColor.brown
segmentedControl.tintColor = UIColor.black
segmentedControl.selectionIndicatorColor = UIColor.gray
segmentedControl.addTarget(self, action: #selector(getter: MemesTableViewController.segmentedControl), for: UIControlEvents.valueChanged)
tableView.tableHeaderView = segmentedControl
segmentedAction()
}
func segmentedAction() {
if segmentedControl.selectedSegmentIndex == 0 {
let postRef = FIRDatabase.database().reference().child("MemesBilder")
postRef.observe(.value, with: { (snapshot) in
var newPost = [PostMitBild]()
for post in snapshot.children {
let Post = PostMitBild(snapshot: post as! FIRDataSnapshot)
newPost.insert(Post, at: 0)
}
self.posts = newPost
DispatchQueue.main.async {
self.tableView.reloadData()
}
}, withCancel: { (error) in
print(error.localizedDescription)
})
}
}
//------------------------------------------
override func viewWillAppear(_ animated: Bool) {
if FIRAuth.auth()?.currentUser == nil {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Login")
self.present(vc, animated: true, completion: nil)
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell
if let seconds = posts[indexPath.row].postDate {
let timestamp = NSDate(timeIntervalSince1970: seconds)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy HH:mm"
cell.uploadDatum.text = dateFormatter.string(from: timestamp as Date)
}
cell.kommentarZähler.text = "Kommentare: \(kommentarArray.count)"
cell.usernameTextField.text = posts[indexPath.row].username
cell.postContent.text = posts[indexPath.row].content
storageRef = FIRStorage.storage().reference(forURL: posts[indexPath.row].userImageUrlString)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
cell.UserImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
let storageRef2 = FIRStorage.storage().reference(forURL: posts[indexPath.row].PostImageUrlString)
storageRef2.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
cell.postImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
return cell
}
//done!!!! ------------------------------------------
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.deleteRows(at: [indexPath], with: .fade)
let ref = posts[indexPath.row].ref
ref!.removeValue()
posts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var numberOfRows = 0
switch segmentedControl.selectedSegmentIndex {
case 0 : numberOfRows = posts.count
case 1: numberOfRows = posts.count
default: break
}
return numberOfRows
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 420.00
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segmentedControl.selectedSegmentIndex == 0 {
if segue.identifier == "addComment" {
let vc = segue.destination as! CommentTableViewController
let indexPath = tableView.indexPathForSelectedRow!
vc.selectedPosts = posts[indexPath.row]
}
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if segmentedControl.selectedSegmentIndex == 0 {
performSegue(withIdentifier: "addComment", sender: self)
}
if segmentedControl.selectedSegmentIndex == 1 {
performSegue(withIdentifier: "addComment", sender: self)
}
if segmentedControl.selectedSegmentIndex == 2 {
performSegue(withIdentifier: "addComment", sender: self)
}
}
func assignArray() {
let otherVC = CommentTableViewController()
kommentarArray = otherVC.commentsArray
print(kommentarArray.count)
}
}
The code for the CommentTableViewController ( where i want to get the count of comments from the array var commentsArray = FotoComment which is already working on this TableView)
import UIKit
import Firebase
import FirebaseAuth
import FirebaseDatabase
import FirebaseStorage
class CommentTableViewController: UITableViewController {
#IBOutlet weak var komentarZähler: UILabel!
#IBOutlet weak var UserImageView: UIImageView!
#IBOutlet weak var usernameTextField: UILabel!
#IBOutlet weak var postImageView: UIImageView!
#IBOutlet weak var postContent: UITextView!
var dataBaseRef : FIRDatabaseReference!
var storageRef : FIRStorageReference!
var commentsArray = [FotoComment]()
var selectedPosts:PostMitBild!
override func viewDidLoad() {
super.viewDidLoad()
configurePost()
let commentRef = selectedPosts.ref!.child("Kommentare")
commentRef.observe(.value, with: { (snapshot) in
var newComments = [FotoComment]()
for item in snapshot.children {
let neuerKommentar = FotoComment(snapshot: item as! FIRDataSnapshot)
newComments.insert(neuerKommentar, at: 0)
}
self.commentsArray = newComments
self.tableView.reloadData()
}, withCancel: { (error) in
print(error.localizedDescription)
})
}
#IBAction func addComment(_ sender: UIBarButtonItem) {
let alertView = UIAlertController(title: "Kommentar", message: "Füge einen Kommentar hinzu", preferredStyle: UIAlertControllerStyle.alert)
alertView.addTextField { (textfield) in
textfield.placeholder = "Einen neuen Kommentar hinzufügen"
}
let sendCommentAction = UIAlertAction(title: "Kommentieren", style: .default) { (action) in
let textfield = alertView.textFields!.first!
let comment = FotoComment(content: textfield.text! , postId: self.selectedPosts.postId , username: (FIRAuth.auth()!.currentUser!.displayName!) , userImageUrlString: String(describing: FIRAuth.auth()!.currentUser!.photoURL!), postDate: (NSDate().timeIntervalSince1970))
let commentRef = self.selectedPosts.ref!.child("Kommentare").childByAutoId()
commentRef.setValue(comment.toAnyObject())
}
let cancelAction = UIAlertAction(title: "Abbrechen", style: .cancel, handler: nil)
alertView.addAction(sendCommentAction)
alertView.addAction(cancelAction)
self.present(alertView, animated: true, completion: nil)
}
// 2----------------------------------------------
func configurePost() {
usernameTextField.text = selectedPosts.username
postContent.text = selectedPosts.content
storageRef = FIRStorage.storage().reference(forURL: selectedPosts.userImageUrlString)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
self.UserImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
let storageRef2 = FIRStorage.storage().reference(forURL: selectedPosts.PostImageUrlString)
storageRef2.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
self.postImageView.image = UIImage (data: data)
}
}
}else {
print(error?.localizedDescription)
}
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
komentarZähler.text = "Kommentare: \(commentsArray.count)"
return commentsArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commentCell", for: indexPath) as! CommentTableViewCell
if let seconds = commentsArray[indexPath.row].postDate {
let timestamp = NSDate(timeIntervalSince1970: seconds)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy HH:mm:ss"
cell.uploadDatum.text = dateFormatter.string(from: timestamp as Date)
}
cell.usernameTextField.text = commentsArray[indexPath.row].username
cell.postContent.text = commentsArray[indexPath.row].content
storageRef = FIRStorage.storage().reference(forURL: commentsArray[indexPath.row].userImageUrlString!)
storageRef.data(withMaxSize: 1 * 1024 * 1024, completion: { (data, error) in
if error == nil {
DispatchQueue.main.async {
if let data = data {
cell.UserImageView.image = UIImage (data: data)
}
self.tableView.reloadData()
}
}else {
print(error?.localizedDescription)
}
})
return cell
}
}
this is how i safe my data in firebase. I want to update the number of comments (kommentarAnzahl which is currently 0) every time a comment is added to the post
You have to convert your Array count to string value to display in cell.
Try this Code,
cell.kommentarZähler.text = String("Kommentare:",kommentarArray.count)
I hope this will work for You.
So you have two separate view controllers namely CommentTableViewController and MemesTableViewController consisting of commentsArray and kommentarArray resepctively. These arrays are of class-scope meaning that once you get outside of your class; for this case it would be leaving the view, then they would be deallocated. Once you enter the view, they'll be re-created again and filled with values.
Since you want to get the number of elements in commentsArray, I'd recommend you create a static variable which would keep track of this. When you make something static you're pretty much making it accessible throughout your entire app/program and changes made to it are reflected across the entire app. In other words, a memory block is reserved to store your variable which will only be de-allocated once you quit the app entirely. You may think of this as an app-scope variable.
Two ways
Lousy Way
Change your commentsArray definition from var commentsArray = [FotoComment]() to static var commentsArray = [FotoComment](). By doing this, then you may access and manipulate the contents of this array from any other class. This is great if you have few elements but what happens when you have tens of thousands or even a million comments? It'll mean that we'll be walking around with huge amounts of data everywhere even when we really don't need it.
Recommended Way
Keep your current commentsArray definition and add this static var numberOfComments: Int = 0 inside your CommentTableViewController. Right after adding your elements to commentsArray, update the element tracker as shown below
CommentTableViewController.numberOfComments = commentsArray.count
Then when you go back to your MemesTableViewController, you can simply delete assignArray(); since we have a global element counter now, and simply amend your cell to this
cell.kommentarZähler.text = String(CommentTableViewController.numberOfComments)
With this, even if you create say another class called FriendsVC, you can still access numberOfComments and even change it as well.
PS: Since numberOfComments is static, whenever and wherever you want to access or amend it, you MUST always first call the class or struct in which it was defined in. For this case, it is inside CommentTableViewController so you need to always do CommentTableViewController.numberOfComments to access it; even when you are inside CommentTableViewController itself. Always remember that.

Resources