Categorize DynamoDB fetched table data for collection view cells - ios

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
})
}

Related

Collection view not reloading after retrieving data

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

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)
}
}

swift: Retrieving images from "Parse"

Im new in Parse(parse.com). I have such kind of table in parse.com:
And I wanna retrieve these 3 images and put are in table view row. And here is my code:
class LeaguesTableViewController: UITableViewController {
var leagues = [PFObject]() {
didSet {
tableView.reloadData()
}
}
var leaguesImage = [NSData]() {
didSet {
tableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadData()
tableView.registerClass(LeaguesTableViewCell.self, forCellReuseIdentifier: "ReusableCell")
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return leagues.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 160
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ReusableCell", forIndexPath: indexPath) as! LeaguesTableViewCell
cell.leagueImage.image = UIImage(data: leaguesImage[indexPath.row])
cell.leagueNameLabel.text = leagues[indexPath.row]["name"] as? String
return cell
}
// MARK: Parse
func loadData() {
let query = PFQuery(className: "Leagues")
query.findObjectsInBackgroundWithBlock { (objects, error) in
if( objects != nil && error == nil) {
// List of leagues
for i in objects! {
self.leagues.append(i)
// Retrieve images
let imageFile = i["image"] as? PFFile
imageFile!.getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
self.leaguesImage.append(imageData)
}
}
}
}
} else if error != nil {
print("Error is: \(error)")
}
}
}
}
Here is my code and from my point of view is everything is ok. But I have error: Index out of the range. My leaguesImages array is empty. Thank you.
Your problem is that leagues and leaguesImages are getting out of sync. Once you retrieve the array from Parse, you are adding the leagues immediately, but leaguesImages are only being added after getDataInBackgroundWithBlock completes.
Instead of downloading the image data right away and storing it in a separate array, I would add a leagues property to your custom cell, and in there I would download the data and apply the image.
Populating an array like you are populating the leaguesImages array is a bad idea when the order matters, because you don't know which one will finish downloading first, maybe the second league image is the smallest, and it will be set as the image for the first league. (PS: image size is not the only thing that dictates how long a download will take)

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].

What is the most efficient way to reuse a UICollectoinView for 60 different Parse.com queries?

I am currently re-querying the UICollectionView every time the UICollectionView is called on viewDidLoad(). It's taking forever to download all of my images. I want to download all of them once at the start of the app and then be able to call them quickly depending on which collection is selected. My code is here:
UICollectionView code:
var exp = ""
class CollectionCollectionViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
images = []
parseObjects = []
imageNames = []
imageExpansions = []
var downloadCards = PFQuery(className: "Cards")
downloadCards.whereKey("ExpansionNumber", equalTo:"\(exp)")
downloadCards.orderByAscending("Number")
downloadCards.limit = 200
downloadCards.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
println("Successfully retrieved \(objects!.count) cards.")
if let objects = objects as? [PFObject] {
for object in objects {
parseObjects.append(object["Image"] as! PFFile)
imageNames.append(object["Number"] as! String)
imageExpansions.append(object["ExpansionNumber"] as! String)
self.collectionView.reloadData()
}
}
} else {
println("Error: \(error!) \(error!.userInfo!)")
}
}
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parseObjects.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell: CardsCollectionViewCell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! CardsCollectionViewCell
parseObjects[indexPath.row].getDataInBackgroundWithBlock{
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
cell.cardsImg.image = image
}
}
//cell.cardLabel.text = imageNames[indexPath.row]
return cell
}
}
Segue and collection identifier code:
var images = [UIImage]()
var parseObjects = [PFFile]()
var imageNames = [String]()
var imageExpansions = [String]()
class selectExpansionViewController: UIViewController {
#IBAction func xy1Button(sender: AnyObject) {
exp = "\(sender.tag)"
self.performSegueWithIdentifier("jumpToCollectionView", sender: self)
}
}
This isn't a collection view reuse issue, it's a data management issue. In particular, you repeatedly download images instead of caching them.
The simple option is to use a PFImageView to handle the image PFFiles, because it caches the images to disk once downloaded. It doesn't give you a great deal of control over that cache however so you might want to consider organising a cache yourself.
In this way you will download only the objects during viewDidLoad, each having a reference to the image file URL and then (in whatever way you choose) check to see if the image for that URL has already been downloaded so you can simply reuse it.

Resources