Firebase Loop through Nested Data and Store in Array - ios

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

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

Unable to download firebase database to my app

I am new to programming in swift. I am trying to make an app which downloads student data from firebase database. I am unable to get the app running. This is my JSON file:
{"classA":[
{
"name": "Student1",
"USN": "1DS16CS095"
},
{
"name": "student2",
"USN": "1DS16CS065"
},
{
"name":"student3",
"USN":"1DS16CS047"
}
]
}
This is my code to download the above JSON file and put it in tableView. Modelstudent is my class where I have my variables name and USN stored. and marksstudentlistTableViewCell is the class I am using to manipulate the labels of my prototype cell.
import UIKit
import Firebase
import FirebaseDatabase
struct StudentData {
let StudentName : String
let StudentUSN : String
}
class marksstudentlist: UIViewController, UITableViewDelegate, UITableViewDataSource{
var FetchedStudentIDs = [StudentData]()
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var _selectedsub: Int!
var selectedsub: Int {
get {
return _selectedsub
}set {
_selectedsub = newValue
}
}
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
if(classselected==0){
ref = Database.database().reference().child("classA")
ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for students in snapshot.children.allObjects as! [DataSnapshot]{
let studentObject = students.value as? [String: String]
let studentname = studentObject?["name"]
let studentUSN = studentObject?["USN"]
let student = Modelstudent(name: studentname , USN: studentUSN)
self.FetchedStudentIDs.insert(StudentData(StudentName :(studentname as? String!)! , StudentUSN : (studentUSN as? String!)! ) , at: 0)
}
self.tableView.reloadData()
}
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FetchedStudentIDs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "marksstudentlistcell", for: indexPath) as! marksstudentlistTableViewCell
cell.namelbl.text = FetchedStudentIDs[indexPath.row].StudentName// Student name comes from Firebase
cell.USNlbl.text = FetchedStudentIDs[indexPath.row].StudentUSN
return cell
}
#IBAction func backbtn(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Using Swift Struct is the best way to get your firebase data inside the tableView,
Let's Get started
on your TableView swift file, above the class and stuff paste this
Struct StudentData {
let StudentObject : String // If it's
let StudentName : String // It returns a string right?
let StudentUSN : String
// do the others.
}
okay so then create a var just down below the class call it
var FetchedStudentIDs = [StudentData]()
then you got the reading method from firebase
ref = Database.database().reference().child("classA")
ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount>0{
self.studentslist.removeAll()
for students in snapshot.children.allObjects as! [DataSnapshot]{
let studentObject = students.value as? [String: AnyObject]
let studentname = studentObject?["name"]
let studentUSN = studentObject?["USN"]
let student = Modelstudent(name: studentname as! String?, USN: studentUSN as! String?) //storing in Modelstudent class
self.
FetchedStudentIDs.insert(StudentData(studentname:StudentName as! String , studentUSN : StudentUSN as! String ) , at: 0) // Student name comes from the struct Above, do the others as this
}
self.tableView.reloadData() // make sure you call this
}
})
return your tableView
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FetchedStudentIDs.count
}
your CellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
cell.exampleLabel.text = FetchedStudentIDs[Indexpath.row].StudentName// Student name comes from Firebase
return cell
}
Hope this Helps

Load images from API

I'm creating an e-commerce app with (Moltin.com) SDK, I set every thing well as it shown in the documentation but now I need to load multi images of single product in table view with custom cell, I set the shown code below and all I can get is a single image my app ignore load the other images view controller code is
class vc: UIViewController , UITableViewDelegate, UITableViewDataSource {
var productDict:NSDictionary?
#IBOutlet weak var tableview: UITableView!
fileprivate let MY_CELL_REUSE_IDENTIFIER = "MyCell"
fileprivate var productImages:NSArray?
override func viewDidLoad() {
super.viewDidLoad()
tableview.delegate = self
tableview.dataSource = self
Moltin.sharedInstance().product.listing(withParameters: productDict!.value(forKeyPath: "url.https") as! [String : Any]!, success: { (response) -> Void in
self.productImages = response?["result"] as? NSArray
self.tableview?.reloadData()
}) { (response, error) -> Void in
print("Something went wrong...")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if productImages != nil {
return productImages!.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: MY_CELL_REUSE_IDENTIFIER, for: indexPath) as! MyCell
let row = (indexPath as NSIndexPath).row
let collectionDictionary = productImages?.object(at: row) as! NSDictionary
cell.setCollectionDictionary(collectionDictionary)
return cell
}
and my custom cell code is
class MyCell: UITableViewCell {
#IBOutlet weak var myImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setCollectionDictionary(_ dict: NSDictionary) {
// Set up the cell based on the values of the dictionary that we've been passed
// Extract image URL and set that too...
var imageUrl = ""
if let images = dict.value(forKey: "images") as? NSArray {
if (images.firstObject != nil) {
imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String
}
}
myImage?.sd_setImage(with: URL(string: imageUrl))
}
Can anyone show me where is the issue that doesn't let me get all the images of my product?
I'm using SWIFT 3, with XCode
In the code below you are always getting one URL from images array (firstObject).
if let images = dict.value(forKey: "images") as? NSArray {
if (images.firstObject != nil) {
imageUrl = (images.firstObject as! NSDictionary).value(forKeyPath: "url.https") as! String
}
}
myImage?.sd_setImage(with: URL(string: imageUrl))
If I understand correctly you should get every image in images array by the indexPath.row of your tableView.
For example add new parameter to method like this:
func setCollection(with dict: NSDictionary, and index: Int) {
// Set up the cell based on the values of the dictionary that we've been passed
// Extract image URL and set that too...
var imageUrlString = ""
if let images = dict.value(forKey: "images") as? Array<NSDictionary>, images.count >= index {
guard let lImageUrlString = images[index]["url.https"] else { return }
imageUrlString = lImageUrlString
}
guard let imageURL = URL(string: imageUrl) else { return }
myImage?.sd_setImage(with: imageURL)
}
Than when call this method in cellForRow just add indexPath.row to the second param.
But if you want show multiple images in one cell you should add more imageViews to the custom cell or use UICollectionView.
Just ping me if I don't understand you clear.

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

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