swift: photos from firebase not showing in table view - ios

I already looked at similar questions and problems and tried what they suggested but none seemed to work and i cant seem to find why nothing is showing in the view. The following is my code.
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var postsTableView: UITableView!
var posts = NSMutableArray()
override func viewDidLoad() {
super.viewDidLoad()
postsTableView.estimatedRowHeight = 521
postsTableView.rowHeight = UITableView.automaticDimension
postsTableView.delegate = self
postsTableView.dataSource = self as UITableViewDataSource
loadData()
}
func loadData() {
Database.database().reference().child("posts").observeSingleEvent(of: .value, with: { (snapshot) in
if let postsDictionary = snapshot.value as? [String: AnyObject]{
for post in postsDictionary {
self.posts.add(post.value)
}
self.postsTableView.reloadData()
}
})
}
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PostTableViewCell
let post = self.posts[indexPath.row] as! [String: AnyObject]
if let imageName = post["posts"] as? String {
let imageRef = Storage.storage().reference().child("posts/\(String(describing: imageName))")
imageRef.getData(maxSize: 15 * 1024 * 1024, completion: { (data, error) -> Void in
if error == nil {
//successful
let image = UIImage(data: data!) //create an image with data sent from database
cell.postImageView.image = image
cell.postImageView.alpha = 0
UIView.animate(withDuration: 0.4, animations: {
cell.postImageView.alpha = 1
})
} else {
//error
print("Error downloading the image: \(String(describing: error?.localizedDescription))")
}
tableView.reloadData()
})
}
return cell
}
}
The screen just shows empty cells of the table view.
This is the console when the app loads.
Also excuse me if I didnt format my question correctly.

As Prettygeek mentioned, first of all, check if the completion of getData is being called. If not, try to retain imageRef outside of tableView(cellForRowAt:) scope (just for debugging).
Check on which thread getData calls its completion, most likely it's different from the main thread, and you shouldn't make calls to UIKit from the thread other than main. DispatchQueue.main.async will get handy there.

Related

Displaying images fetched from firebase in tableview

I am trying to retrieve data from firebase and display them in a tableview but my code for some reason is not working. I am getting the images and appending them into a list but that list is not having any data outside the else block.
With my implementation, I am getting a blank tableview. Could someone help with me this please?
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource{
#IBOutlet weak var tableView: UITableView!
let db = Firestore.firestore()
var usersReference = Firestore.firestore().collection("users")
var storageReference = Storage.storage().reference()
var imagesList = [UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
usersReference.addSnapshotListener { [self] (querySnapshot, error) in
querySnapshot?.documents.forEach() {
(document) in
let uid = document.data()["uid"] as! String
let imagePath = "profilePictures/\(uid).jpg"
let imgRef = storageReference.child(imagePath)
imgRef.getData(maxSize: 1 * 2048 * 2048) { data, error in
if let error = error {
print(String(describing: error))
}
else {
DispatchQueue.main.async {
let image = UIImage(data: data!)
imagesList.append(image!)
print("inside viewDidLoad: ", imagesList.count)
}
}
}
}
}
print(imagesList.count)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return imagesList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "imageCell", for: indexPath)
print("cellForRowAt: ",imagesList.count)
cell.textLabel?.text = "\(indexPath.row)"
cell.imageView?.image = imagesList[indexPath.row]
return cell
}
This is how my simulator looks like when I run the code: compiled code .

Proper Placement of dispatchGroup to reloadData

I have a tableview function that is pulling data from a database to render cells. I want to accomplish the goal of not reloading my tableview so much. I learned that dispatch groups would be the way to go beause I don't want to return to the completion block that reloads the tableView until all the data has been pulled however when I use the dispatchGroup it never reaches the completion it just stops. The placement of my variables may be in the wrong place but i just can't really see where I should put it. I have been moving it to different places and still nothing.
import UIKit
import Firebase
class FriendsEventsView: UITableViewController{
var cellID = "cellID"
var friends = [Friend]()
var attendingEvents = [Event]()
//label that will be displayed if there are no events
var currentUserName: String?
var currentUserPic: String?
var currentEventKey: String?
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Friends Events"
view.backgroundColor = .white
// Auto resizing the height of the cell
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: #imageLiteral(resourceName: "close_black").withRenderingMode(.alwaysOriginal), style: .done, target: self, action: #selector(self.goBack))
tableView.register(EventDetailsCell.self, forCellReuseIdentifier: cellID)
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
DispatchQueue.global(qos: .background).async {
print("This is run on the background queue")
self.fetchEventsFromServer { (error) in
if error != nil {
print(error)
return
} else {
DispatchQueue.main.async {
self.tableView.reloadData()
print("This is run on the main queue, after the previous code in outer block")
}
}
}
}
}
#objc func goBack(){
dismiss(animated: true)
}
override func numberOfSections(in tableView: UITableView) -> Int {
// print(friends.count)
return friends.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// print(friends[section].events.count)
return friends[section].collapsed ? 0 : friends[section].events.count
}
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! EventDetailsCell? ?? EventDetailsCell(style: .default, reuseIdentifier: cellID)
// print(indexPath.row)
cell.details = friends[indexPath.section].events[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
// print(section)
header.arrowLabel.text = ">"
header.setCollapsed(friends[section].collapsed)
print(friends[section].collapsed)
header.section = section
// header.delegate = self
header.friendDetails = friends[section]
return header
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func fetchEventsFromServer(_ completion: #escaping (_ error: Error?) -> Void ){
//will grab the uid of the current user
guard let myUserId = Auth.auth().currentUser?.uid else {
return
}
let ref = Database.database().reference()
//checking database for users that the current user is following
ref.child("following").child(myUserId).observeSingleEvent(of: .value, with: { (followingSnapshot) in
//handling potentail nil or error cases
guard let following = followingSnapshot.children.allObjects as? [DataSnapshot]
else {return}
//validating if proper data was pulled
let group = DispatchGroup()
for followingId in following {
group.enter()
UserService.show(forUID: followingId.key, completion: { (user) in
PostService.showFollowingEvent(for: followingId.key, completion: { (event) in
self.attendingEvents = event
var friend = Friend(friendName: (user?.username)!, events: self.attendingEvents, imageUrl: (user?.profilePic)!)
self.friends.append(friend)
})
})
}
this loop should return to the completon block in viewWillAppear following the execution of this if statement
if self.friends.count == following.count{
group.leave()
let result = group.wait(timeout: .now() + 0.01)
//will return this when done
completion(nil)
}
}) { (err) in
completion(err)
print("Couldn't grab people that you are currently following: \(err)")
}
}
Any help is greatly appreciated
You want to place the group.leave() inside of the PostService.showFollowingEvent callback.
Now you call enter following.count-times, but you call leave only once. For the group to continue you have to leave the group as many times as you entered it:
for followingId in following {
group.enter()
UserService.show(forUID: followingId.key, completion: { (user) in
PostService.showFollowingEvent(for: followingId.key, completion: { (event) in
self.attendingEvents = event
var friend = Friend(friendName: (user?.username)!, events: self.attendingEvents, imageUrl: (user?.profilePic)!)
self.friends.append(friend)
// leave here
group.leave()
})
})
}
Moreover, I would not recommend using group.wait since you are facing a possible deadlock. If any of the callbacks that are supposed to call group.leave are happening on the same thread as group.wait was called, they will never get called and you will end up with the frozen thread. Instead, use group.notify:
group.notify(queue: DispatchQueue.main) {
if self.friends.count == following.count {
completion(nil)
}
}
This will allow the execution on the main thread, but once all the tasks are finished, it will execute the provided callback closure.

Limit the amount of cells shown in tableView, load more cells when scroll to last cell

I'm trying to set up a table view that only shows a specific amount of cells. Once that cell has been shown, the user can keep scrolling to show more cells. As of right now I'm retrieving all the JSON data to be shown in viewDidLoad and storing them in an array. Just for example purposes I'm trying to only show 2 cells at first, one the user scrolls to bottom of screen the next cell will appear. This is my code so far:
class DrinkViewController: UIViewController {
#IBOutlet weak var drinkTableView: UITableView!
private let networkManager = NetworkManager.sharedManager
fileprivate var totalDrinksArray: [CocktailModel] = []
fileprivate var drinkImage: UIImage?
fileprivate let DRINK_CELL_REUSE_IDENTIFIER = "drinkCell"
fileprivate let DRINK_SEGUE = "detailDrinkSegue"
var drinksPerPage = 2
var loadingData = false
override func viewDidLoad() {
super.viewDidLoad()
drinkTableView.delegate = self
drinkTableView.dataSource = self
networkManager.getJSONData(function: urlFunction.search, catagory: urlCatagory.cocktail, listCatagory: nil, drinkType: "margarita", isList: false, completion: { data in
self.parseJSONData(data)
})
}
}
extension DrinkViewController {
//MARK: JSON parser
fileprivate func parseJSONData(_ jsonData: Data?){
if let data = jsonData {
do {
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String : AnyObject]//Parses data into a dictionary
// print(jsonDictionary!)
if let drinkDictionary = jsonDictionary!["drinks"] as? [[String: Any]] {
for drink in drinkDictionary {
let drinkName = drink["strDrink"] as? String ?? ""
let catagory = drink["strCategory"] as? String
let drinkTypeIBA = drink["strIBA"] as? String
let alcoholicType = drink["strAlcoholic"] as? String
let glassType = drink["strGlass"] as? String
let drinkInstructions = drink["strInstructions"] as? String
let drinkThumbnailUrl = drink["strDrinkThumb"] as? String
let cocktailDrink = CocktailModel(drinkName: drinkName, catagory: catagory, drinkTypeIBA: drinkTypeIBA, alcoholicType: alcoholicType, glassType: glassType, drinkInstructions: drinkInstructions, drinkThumbnailUrl: drinkThumbnailUrl)
self.totalDrinksArray.append(cocktailDrink)
}
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
DispatchQueue.main.async {
self.drinkTableView.reloadData()
}
}
//MARK: Image Downloader
func updateImage (imageUrl: String, onSucceed: #escaping () -> Void, onFailure: #escaping (_ error:NSError)-> Void){
//named imageData because this is the data to be used to get image, can be named anything
networkManager.downloadImage(imageUrl: imageUrl, onSucceed: { (imageData) in
if let image = UIImage(data: imageData) {
self.drinkImage = image
}
onSucceed()//must call completion handler
}) { (error) in
onFailure(error)
}
}
}
//MARK: Tableview Delegates
extension DrinkViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return numberOfRows
return drinksPerPage
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = drinkTableView.dequeueReusableCell(withIdentifier: DRINK_CELL_REUSE_IDENTIFIER) as! DrinkCell
//get image from separate url
if let image = totalDrinksArray[indexPath.row].drinkThumbnailUrl{//index out of range error here
updateImage(imageUrl: image, onSucceed: {
if let currentImage = self.drinkImage{
DispatchQueue.main.async {
cell.drinkImage.image = currentImage
}
}
}, onFailure: { (error) in
print(error)
})
}
cell.drinkLabel.text = totalDrinksArray[indexPath.row].drinkName
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let image = totalDrinksArray[indexPath.row].drinkThumbnailUrl{
updateImage(imageUrl: image, onSucceed: {
}, onFailure: { (error) in
print(error)
})
}
performSegue(withIdentifier: DRINK_SEGUE, sender: indexPath.row)
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = drinksPerPage
if indexPath.row == lastElement {
self.drinkTableView.reloadData()
}
}
}
I saw this post: tableview-loading-more-cell-when-scroll-to-bottom and implemented the willDisplay function but am getting an "index out of range" error.
Can you tell me why you are doing this if you are getting all results at once then you don't have to limit your display since it is automatically managed by tableview. In tableview all the cells are reused so there will be no memory problem. UITableViewCell will be created when it will be shown.
So no need to limit the cell count.
I dont now what you are doing in your code but:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = drinksPerPage // no need to write this line
if indexPath.row == lastElement { // if block will never be executed since indexPath.row is never equal to drinksPerPage.
// As indexPath starts from zero, So its value will never be 2.
self.drinkTableView.reloadData()
}
}
Your app may be crashing because may be you are getting only one item from server.
If you seriously want to load more then you can try this code:
Declare numberOfItem which should be equal to drinksPerPage
var numberOfItem = drinksPerPage
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return numberOfRows
return numberOfItem
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == numberOfItem - 1 {
if self.totalDrinksArray.count > numberOfItem {
let result = self.totalDrinksArray.count - numberOfItem
if result > drinksPerPage {
numberOfItem = numberOfItem + drinksPerPage
}
else {
numberOfItem = result
}
self.drinkTableView.reloadData()
}
}
}

empty tableview cell for at index path row is not called

All the tableview functions are working except cell for row index path .
The problem maybe that foods array is empty so the number for rows is 0 so the cell for row at index path is not called
#IBOutlet weak var foooods: UITableView!
var databaseref = Database.database().reference()
var img : AnyObject?
var foods = [String?]()
override func viewDidLoad() {
super.viewDidLoad()
self.databaseref.child("basic food").observe(.childAdded, with: {( snap: DataSnapshot) in
let snapp = snap.value as! [String:AnyObject]
if let x = snapp["name"] as! String? {
self.foods.insert(x, at: 0)
//self.foods.append(x)
}
})
self.foooods.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.foods.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("difufuehf")
let cell : foodsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "aupa", for:indexPath) as! foodsTableViewCell
print("fufvksdfvysdgfvjdsgfdsygfvds,jhvjsdvsdjvguydsfgdsylfgdsyfgsdlygfsiygf")
if let foo = foods[indexPath.row] {
print(foo)
cell.food.text = foo
}
return cell
}
This must be a duplicate but I can't find one.
Your issue is that you call reloadData in the wrong place which results in it being called far too soon. You need to call it inside the completion block, after you update your data model.
And you need to make sure it gets called on the main queue.
override func viewDidLoad() {
super.viewDidLoad()
self.databaseref.child("basic food").observe(.childAdded, with: {( snap: DataSnapshot) in
if let snapp = snap.value as? [String:Any], let x = snapp["name"] as? String {
self.foods.insert(x, at: 0)
//self.foods.append(x)
DispatchQueue.main.async {
self.foooods.reloadData()
}
}
})
}
Note that I also fixed the way the value is obtained. You really need to avoid force unwrapping and force casting.

Parse Values in TableViewCell not updating

Very big thanks in advance to anyone who can help me, very much appreciated!
I am building a dating app and I am trying to have my matches load in a table after a parse query. The expected result is that the table view contains the match image and the match ID. Right now I have code for that working perfectly below.
import UIKit
import Parse
class MyListViewController: UIViewController, UITableViewDataSource,
UITableViewDelegate {
var images = [UIImage]()
var userIds = [String]()
#IBOutlet weak var tView: UITableView!
#IBAction func toSwiperButton(_ sender: Any) {
performSegue(withIdentifier: "ToSwiper", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
let query = PFUser.query()
query?.whereKey("objectId", containedIn: PFUser.current()?["accepted"]
as! [String])
query?.findObjectsInBackground(block: { (objects, error) in
if let users = objects {
for object in users {
if let user = object as? PFUser {
let imageFile = user["photo"] as! PFFile
imageFile.getDataInBackground(block: { (data, error) in
if let imageData = data {
self.images.append(UIImage(data: imageData)!)
self.userIds.append(user.objectId!)
self.tView.reloadData()
}
})
}
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
internal func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return images.count
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! PDPLIstViewTableViewCell
cell.Image.image = images[indexPath.row]
cell.id.text = userIds[indexPath.row]
return cell
}
}
The problem arises when I try to include two additional labels onto the table view for "name" and for "age" - I cant seem to figure the correct way to call them in the query in Parse along with the working photo query.
The result I want is for every cell in the table to have an image (code is working) Id (code is working) Name (code not working) and age (code not working)
By "not working" what I mean is I get a ton of errors when I try o create the variable for age from the parse data so I can pass it into the array so that my tableview can display the text next to the image.
Here is what I have been using for non working code on the "Age" label, I believe the error is where I am trying to pull the name/age using "= data" and I have to use a different term?
import UIKit
import Parse
class MyListViewController: UIViewController, UITableViewDataSource,
UITableViewDelegate {
var images = [UIImage]()
var userIds = [String]()
var name = [String]()
var age = [String]()
#IBOutlet weak var tView: UITableView!
#IBAction func toSwiperButton(_ sender: Any) {
performSegue(withIdentifier: "ToSwiper", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
let query = PFUser.query()
query?.whereKey("objectId", containedIn: PFUser.current()?["accepted"]
as! [String])
query?.findObjectsInBackground(block: { (objects, error) in
if let users = objects {
for object in users {
if let user = object as? PFUser {
let ageFile = user["age"] as! PFFile
ageFile.getDataInBackground(block: { (data, error) in
if let ageData = data {
self.age.append(UILabel(data: ageData)!)
}
let imageFile = user["photo"] as! PFFile
imageFile.getDataInBackground(block: { (data, error) in
if let imageData = data {
self.images.append(UIImage(data: imageData)!)
self.userIds.append(user.objectId!)
self.age.append(String(data: ageFile))
self.tView.reloadData()
}
})
}
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
internal func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
return images.count
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for:
indexPath) as! PDPLIstViewTableViewCell
cell.image.image = images[indexPath.row]
cell.id.text = userIds[indexPath.row]
cell.name.text = name[indexPath.row]
cell.age.text = age[indexPath.row]
return cell
}
}
You are reloading the tableview in the loop (a lot), also you do not reload when ageData is complete. Try reloading the tableview once, once the query is done. In the:
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
guard let ageFile = age[indexPath.row] as? PFFile else { return }
ageFile.getDataInBackground(block: { (data, error) in
if let ageData = data {
cell.age.text = ageData
}
}

Resources