Collection view not reloading after retrieving data - ios

Situation: I'm pulling data from Firebase. After pulling the data, I want to update/reload my collectionView table.
Problem: collectionView doesn't update. Here are the codes with a bit of explanation.
var allProducts = [Product]()
override func viewDidLoad() {
super.viewDidLoad()
mostPopularCollectionView.dataSource = self
mostPopularCollectionView.delegate = self
getAllProducts { (returnedProductArray) in
self.allProducts = returnedProductArray
self.mostPopularCollectionView.reloadData()
}
}
The function getAllProducts works fine. If I print allProducts.count within the closure, I get the right number(3).
If I print allProducts.count outside the closure, my count is zero.
I tried putting the getAllProducts function in viewWillAppear but it didn't solve the problem
extension FeedTableViewController: UICollectionViewDataSource, UICollectionViewDelegate
{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 3
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mostPopularCell", for: indexPath) as? MostPopularCollectionViewCell else {return UICollectionViewCell()}
if allProducts.count > 0 {
let product : Product = allProducts[indexPath.row]
if let productImageUrl = product.imageUrlArray.first {
cell.upadateCellUI(forProductName: product.title, forProductImage: productImageUrl, forProductPrice: product.price)
}
return cell
} else {
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let productVC = UIStoryboard.init(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "productVC") as! ProductViewController
productVC.product = allProducts[indexPath.row]
present(productVC, animated: true, completion: nil)
}
}
The good news is that when I click on any item, the right product is selected when the next viewController appears.
So the only issue is how do I get the collectionView to reload after data is retrieved from Firebase? Any help is very much appreciated
This is the getAllProducts function used to retrieve all the data from Firebase.
//MARK:- Retrieve all products from Firebase
func getAllProducts (handler: #escaping (_ allProducts: [Product]) -> ()) {
//TODO:- Create an empty array to store all product fetched from Firebase
var productArray = [Product]()
var imageUrlArray = [String]()
//TODO:- Create reference to Firebase database
let DB = Database.database().reference()
//TODO:- Create reference to products
let REF_PRODUCTS = DB.child("Product")
//TODO:- Snapshot of all products in database
REF_PRODUCTS.observeSingleEvent(of: .value) { (allProductsSnapshot) in
guard let allProductsSnapshot = allProductsSnapshot.children.allObjects as? [DataSnapshot] else {return}
for product in allProductsSnapshot {
let title = product.childSnapshot(forPath: "name").value as! String
let price = product.childSnapshot(forPath: "price").value as! String
let id = product.childSnapshot(forPath: "id").value as! Int
let viewCount = product.childSnapshot(forPath: "viewCount").value as! Int
let description = product.childSnapshot(forPath: "description").value as! String
let REF_IMAGEURL = REF_PRODUCTS.child(String(id)).child("image")
REF_IMAGEURL.observeSingleEvent(of: .value, with: { (allImageUrlSnapshot) in
guard let allImageUrlSnapshot = allImageUrlSnapshot.children.allObjects as? [DataSnapshot] else {return}
for imageUrl in allImageUrlSnapshot {
let imageUrl = imageUrl.value as! String
imageUrlArray.append(imageUrl)
}
})
let product = Product(title: title, price: price, imageUrlArray: imageUrlArray, description: description, viewCount: viewCount, id: id)
productArray.append(product)
}
handler(productArray)
}
}

You should always update your UI elements on main thread. No exception here as well. Just execute the reload code on main thread.
dispatch_async(dispatch_get_main_queue(), {
self.mostPopularCollectionView.reloadData()
})
For Swift 3:
DispatchQueue.main.async {
self.mostPopularCollectionView.reloadData()
}

your viewDidLoad is look like below:
var allProducts = [Product]()
override func viewDidLoad() {
super.viewDidLoad()
mostPopularCollectionView.dataSource = self
mostPopularCollectionView.delegate = self
getAllProducts { (returnedProductArray) in
self.allProducts = returnedProductArray
}
self.mostPopularCollectionView.reloadData()
}

This may be due the auto layout issue, I stuck in the same case and resolving the auto layout issue for the cell, enable debug log for view on the xcode. and see if there is any auto layout issue is there, remember the size of the content should be less than content of collection view

Related

Observe call from Firebase not updating value of dictionary after function exit

I'm trying to learn iOS programming so I thought it would be a good idea to emulate instagrams feed. Everyone uses this basic feed and I would like to know how to do it.
The basic idea is to have one image/text post show up in a single column. Right now I have a a single image to be shown.
I'm currently extracting the image url correctly from firebase. The only issue is that my CollectionView still is showing up empty. I started this project months ago and I forget where the tutorial is at. Please help me fill in the blanks. Here is the code:
import UIKit
import SwiftUI
import Firebase
import FirebaseUI
import SwiftKeychainWrapper
class FeedViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{
#IBOutlet weak var collectionview: UICollectionView!
//var posts = [Post]()
var posts = [String](){
didSet{
collectionview.reloadData()
}
}
var following = [String]()
var posts1 = [String]()
var userStorage: StorageReference!
var ref : DatabaseReference!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
posts1 = fetchPosts()
//let myIndexPath = IndexPath(row: 0, section: 0)
//collectionView(collectionview, cellForItemAt: myIndexPath)
//print(self.posts1.count)
}
func fetchPosts() -> [String]{
let uid = Auth.auth().currentUser!.uid
let ref = Database.database().reference().child("posts")
let uids = Database.database().reference().child("users")
uids.observe(DataEventType.value, with: { (snapshot) in
let dict = snapshot.value as! [String:NSDictionary]
for (_,value) in dict {
if let uid = value["uid"] as? String{
self.following.append(uid)
}
}
ref.observe(DataEventType.value, with: { (snapshot2) in
let dict2 = snapshot2.value as! [String:NSDictionary]
for(key, value) in dict{
for uid2 in self.following{
if (uid2 == key){
for (key2,value2) in value as! [String:String]{
//print(key2 + "this is key2")
if(key2 == "urlToImage"){
let urlimage = value2
//print(urlimage)
self.posts1.append(urlimage)
self.collectionview.reloadData()
print(self.posts1.count)
}
}
}
}
}
})
})
//ref.removeAllObservers()
//uids.removeAllObservers()
print("before return")
print(self.posts1.count)
return self.posts1
override func viewDidLayoutSubviews() {
collectionview.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) ->Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return posts1.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PostCell", for: indexPath) as! PostCell
cell.postImage.sd_setImage(with: URL(string: posts1[indexPath.row]))
//creating the cell
//cell.postImage.downloadImage(from: self.posts[indexPath.row])
// let storageRef = Storage.storage().reference(forURL: self.posts[indexPath.row].pathToImage)
//
//
print("im trying")
//let stickitinme = URL(fileURLWithPath: posts1[0])
//cell.postImage.sd_setImage(with: stickitinme)
//cell.authorLabel.text = self.posts[indexPath.row].author
//cell.likeLabel.text = "\(self.posts[indexPath.row].likes) Likes"
return cell
}
#IBAction func signOutPressed(_sender: Any){
signOut()
self.performSegue(withIdentifier: "toSignIn", sender: nil)
}
#objc func signOut(){
KeychainWrapper.standard.removeObject(forKey:"uid")
do{
try Auth.auth().signOut()
} catch let signOutError as NSError{
print("Error signing out: %#", signOutError)
}
dismiss(animated: true, completion: nil)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
UPDATE
The observe call is not updating the value of posts (the dictionary). Once the observe call exits, the value of posts is set back to empty.
PostCell class as asked:
import UIKit
class PostCell: UICollectionViewCell {
#IBOutlet weak var postImage: UIImageView!
#IBOutlet weak var authorLabel: UILabel!
#IBOutlet weak var likeLabel:UILabel!
#IBOutlet weak var likeBtn:UIButton!
#IBOutlet weak var unlikeBtn:UIButton!
#IBAction func likePressed (_ sender: Any){
}
#IBAction func unlikePressed(_sender: Any){
}
}
I think the problem is:
Your collectionView dataSource is called only once. Since the image url loading is asynchronous, you will need to refresh your collectionview every time new data is appended to your datasource array like this:
self.posts.append(urlimage)
collectionView.reloadData()
or:
var posts = [UIImage](){
didSet{
collectionView.reloadData()
}
}
Hope this helps.
Edit update:
Regarding the asynchronous calls, i think you should use escaping closure that runs the code block once the network request receives a response.
First separate the network call functions like:
func fetchUsers(completion: #escaping(_ dictionary: [String: NSDictionary])->()){
let uid = Auth.auth().currentUser!.uid
let uids = Database.database().reference().child("users")
uids.observe(DataEventType.value, with: { (snapshot) in
let dict = snapshot.value as! [String:NSDictionary]
completion(dict)
})
}
func fetchURLS(completion: #escaping(_ dictionary: [String: String])->()){
let ref = Database.database().reference().child("posts")
ref.observe(DataEventType.value, with: { (snapshot2) in
let dict2 = snapshot2.value as! [String:String]
completionTwo(dict2)
})
}
Then, the parsing functions:
func parseUsers(dictionary: [String: NSDictionary]){
for (_,value) in dictionary {
if let uid = value["uid"] as? String{
self.following.append(uid)
}
}
fetchURLS { (urlDictionary) in
self.parseImageURLS(dictionary: urlDictionary)
}
}
func parseImageURLS(dictionary: [String: String]){
for(key, value) in dictionary{
for uid2 in self.following{
if (uid2 == key){
for (key2,value2) in value as! [String:String]{
//print(key2 + "this is key2")
if(key2 == "urlToImage"){
let urlimage = value2
//print(urlimage)
self.posts1.append(urlimage)
self.collectionview.reloadData()
print(self.posts1.count)
}
}
}
}
}
}
Then you add:
fetchUsers { (usersDictionary) in
self.parseUsers(dictionary: usersDictionary)
}
in viewDidLoad()
Hope this solves your problem. On a side note: I recommend using models and separating the network calls in a different file. Feel free to ask any questions.
I figured out how to do it after more searching.
I was incorrectly assuming that the CollectionView is loaded after the viewDidLoad() function is done. The helper classes for a CollectionView are called to a call of reloadData.
I observed that my reloadData call wasn't being called. In order to make this work, I add 2 lines of code to the viewDidLoad function:
collectionview.delegate = self
collectionview.dataSource = self
With this change, the images now load.

Not able to save data from Firebase database to an Array

*I'm fairly new to swift
I'm currently using Swift 4, Xcode 9, and Firebase. My goal is to create an app that stores data in a list, displays it in a table view, and allows the user to add more data to the list. I'm stuck on the displaying data part, I created a function that is supposed to get the data from the database, then add it into an array so that I can display individual parts of it on a custom table view cell. Here's my code:
class OrdersPage: UIViewController, UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return orders.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "orderCell", for: indexPath) as! OrderCell
cell.setOrder(order: orders[indexPath.row])
print("Adding new cell")
return cell
}
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var orders = [Order]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
self.ref = Database.database().reference()
orders = getOrders()
}
func getOrders() -> [Order] {
var tempArray = [Order]()
ref.child("Orders").observe(.value) { (snapshot) in
for child in snapshot.children {
let orderDB = child as! DataSnapshot
let orderDict = orderDB.value as! [String: Any]
let name = orderDict["name"] as! String
let date = orderDict["date"] as! String
let time = orderDict["time"] as! String
let hotel = orderDict["hotel"] as! String
let room = orderDict["room"] as! String
let airport = orderDict["airport"] as! String
let agent = orderDict["agent"] as! String
let price = orderDict["price"] as! String
//let submitted = orderDict["submitted"] as! String
tempArray.append(Order(name: name, date: date, time: time, hotel: hotel, room: room, airport: airport, agent: agent, price: price))
}
}
return tempArray
}
Based off of my testing, the issue is that the orders array doesn't contain anything when the numberOfRowsInSection is called so it doesn't create any cells in the table view. I'm not sure why it's not working as it should and have been stuck on this for quite some time now, any help is appreciated.
getOrders() is Asynchronous call so you need to reload your table after you got data from server.
Here is the way you can achieve that.
Replace:
func getOrders() -> [Order]
with
func getOrders()
And your getOrders method will look like:
func getOrders() {
ref.child("Orders").observe(.value) { (snapshot) in
for child in snapshot.children {
let orderDB = child as! DataSnapshot
let orderDict = orderDB.value as! [String: Any]
let name = orderDict["name"] as! String
let date = orderDict["date"] as! String
let time = orderDict["time"] as! String
let hotel = orderDict["hotel"] as! String
let room = orderDict["room"] as! String
let airport = orderDict["airport"] as! String
let agent = orderDict["agent"] as! String
let price = orderDict["price"] as! String
//let submitted = orderDict["submitted"] as! String
//Add your data into array
self.orders.append(Order(name: name, date: date, time: time, hotel: hotel, room: room, airport: airport, agent: agent, price: price))
}
//Reload your tableView here
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
I have updated inner code. Check comments.
Now in your viewDidLoad method Replace:
orders = getOrders()
With
getOrders()
You can use didSet during define your variable of self.orders for reloading UITableView
Here your table will automatically reload when any data is assigned to self.orders
Replace your declaration
var orders = [Order]()
with below code
var orders : [Order] = [] {
didSet {
tableView.reloadData()
}
}

Categorize DynamoDB fetched table data for collection view cells

I am working on a services app in which user creates a post whose details are saved in a dynamoDb table. I have fetched all the data in the table and now i want to display the data in collection view controller such that each cell represents single post. Now i am not sure how to segregate every single post from that data and provide it to collection view. My table fields are:
Table_Screenshot
My code is:
import UIKit
import AWSDynamoDB
class ProvidingViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var PCV: UICollectionView!
let db = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
var counter:Int = 0
var imagex = ["UserIcon.png", "chat2.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png","UserIcon.png", "delete.png"]
var images:[String] = []
override func viewDidLoad() {
super.viewDidLoad()
scanner()
}
///
func scanner(){
scanExpression.limit = 2000
db.scan(PostDetails.self, expression: scanExpression).continueWith(block: { (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result!
//use the results
for item in paginatedOutput.items as! [PostDetails] {
self.counter = paginatedOutput.items.count
self.images.append(item.userId!)
}
if ((task.error) != nil) {
print("Error: Could not fetch PostDetails table data")
}
return nil
}
return nil
})
}
///
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = PCV.dequeueReusableCell(withReuseIdentifier: "c", for: indexPath) as! CellsCollectionViewCell
cell.ProvImage.image = UIImage(named: imagex[indexPath.row])
cell.ProvLabel.text = images[indexPath.row]
return cell
}
}
I have images array in which i am fetching data. When i print out the array it has data but when i assign it to collection view controller, screen is displayed empty i.e no cells. Please help. Thanks
The real issue you are facing is that when View loads the images array is empty and CollectionView loads empty and when you are loading images in the array in the scanner method you are not calling reloadData for CollectionView that is why you are not able to see anything in the CollectionView after data is being loaded into your array. I am updating your scanner method , try this and it will work.
func scanner(){
scanExpression.limit = 2000
db.scan(PostDetails.self, expression: scanExpression).continueWith(block: { (task:AWSTask!) -> AnyObject! in
if task.result != nil {
let paginatedOutput = task.result!
//use the results
for item in paginatedOutput.items as! [PostDetails] {
self.counter = paginatedOutput.items.count
self.images.append(item.userId!)
}
//This line is important because it tells collectionview that i have new data so please refresh.
PCV.reloadData()
if ((task.error) != nil) {
print("Error: Could not fetch PostDetails table data")
}
return nil
}
return nil
})
}

Firebase Loop through Nested Data and Store in Array

I'm trying to save a list of image url's to an empty array of strings to then show in a collection view. I'm having trouble looping through the dictionary to store the URLs.
I get the Firebase data in the EncounterTableViewController.swift
, then have another detailed view controller EncounterDetailViewController.swift that has an EncounterCollectionViewCell.swift
Encounter.swift
class Encounter {
...
...
var images: [String] = []
}
EncounterTableViewController.swift
func showAllEncounters() {
// Firebase tableview data
FIRDatabase.database().reference().child("encounters").observeSingleEvent(of: .value, with: { (snapshot) in
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
guard let restDict = rest.value as? [String: Any] else { continue }
let encounter = Encounter()
...
...
let mediaDict = restDict["media"] as! [[String:Any]]
// need to find nested images and set them to encounter.images here
self.encounters.append(encounter)
self.tableView.reloadData()
}
})
}
EncounterDetailViewController.swift
private let reuseIdentifier = "imageCell"
class EncounterDetailViewController: UIViewController,
UICollectionViewDataSource, UICollectionViewDelegate {
// MARK: - Properties
var selectedEncounter: Encounter?
// MARK: - View did load
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - UICollectionViewDataSource
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return (selectedEncounter?.images.count)!
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EncounterCollectionViewCell
cell.imageView.sd_setImage(with: URL(string: (selectedEncounter?.images[indexPath.row])!))
return cell
}
Encounter Data structure
encounters
-12
-name: "shark"
-length: "3"
-media
-0
-id: "3242"
-url: "http://google.com"
-thumb-url: "http://thisurl.com"
-1
-id: "4252"
-url: "http://google.com"
-thumb-url: "http://thisurl.com"
Instead of for loop, simplest solution is to use flatMap.
let mediaDict = restDict["media"] as! [[String:Any]]
images = mediaDict.flatMap { $0["thumb_url"] as? String }
This single line solution will reduce your code of for loop but if still want to go with loop then you can make it like this.
for media in mediaDict {
if let url = media["thumb_url"] as? String {
images.append(url)
}
}

Getting Data from Core Data with Swift

I can't seem to get this right. I want to get core data from my Database and display all in table view. Running this only displays the last ID multiple times on my table. Could someone advise what I'm doing wrong and/or possibly assist? Thanks.
import Foundation
import CoreData
extension MyFavourites {
#NSManaged var id: String?
}
-
var myFavs : [MyFavourites]?
override func viewDidLoad() {
super.viewDidLoad()
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let freq = NSFetchRequest(entityName: "MyFavourites")
freq.returnsObjectsAsFaults = false
do {
myFavs = try context.executeFetchRequest(freq) as? [MyFavourites]
} catch _ {
myFavs = nil
}
tableView.reloadData()
}
-
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (myFavs?.count)!
}
-
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
if myFavs!.count > 0 {
for result: AnyObject in myFavs! {
if let favID: String = result.valueForKey("id") as? String {
cell.textLabel?.text = favID
}
}
} else {
print("No Record")
}
return cell
}
If I am reading your code correctly, it will display last recorded favId in all cells. The cellForRowAtIndexPath asks you for value for current cell, but instead of providing that, you loop through all of them and repeatedly assign the same label with favID rewriting it multiple times. At the end of the cycle the label will have the last ID from the list.
You need to remove the loop and assign cell.label.text with ID value from myFavs[indexPath.row].

Resources