How to wait for an array to be filled in Swift - ios

I am writing an app in Swift and having problems filling a tableview the right way.
I am getting my data from Firestore and have a class to help me get that data. The basic process is that I have a getProducts function which sets a local array variable with products. The next step is to create an array of objects in my tableview class but my there seems to be a fault where my tableview gets build before my function has the time to load in the array.
So my loadProducts fills the array products but my count seems to be 0.
Hope you guys can help, thank you in advance
My code:
class ProductTableViewController: UITableViewController {
var products = [Product]()
override func viewDidLoad() {
super.viewDidLoad()
loadProducts()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
private func loadProducts(){
let dbhelper = DBHelper()
dbhelper.getProducts(){ success in
self.products = dbhelper.products
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "ProductTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ProductTableViewCell else {
fatalError("The dequeued cell is not an instance of ProductTableViewCell.")
}
let product = products[indexPath.row]
cell.titleLabel.text = product.Titel
cell.priceLabel.text = product.Prijs
return cell
}
}

Inside you load products func add a table view reload statment in order to reload the table with your new data :
private func loadProducts(){
let dbhelper = DBHelper()
dbhelper.getProducts(){ success in
self.products = dbhelper.products
Self.tableView.reloadData()
}
}

Related

Data from firebase not populating in my table view

So I recently asked a question regarding firebase - but have refactored my code quite a bit as I wanted all of my firebase methods to be done in the same place. I am having the following issue after the refactor...
Data from my firebase database is not populating my tableview. I'm not too sure why this would be, as it was working fine before I moved the method to a separate file from my table view(for cleaner code). All I did was move the method that populates the array to a separate file, return an array and then reload the tableview after calling the method. Below is the code in question:
In my FireBaseMethods class
//-------------- POPULATE TABLE ARRAY -----------------//
public func populateConsumableTableArray() -> [Consumable]{
var tableArray = [Consumable]()
//let the object populate itself.
ref.child("Consumables").observe(.childAdded, with: { snapshot in
let dataChange = snapshot.value as? [String:AnyObject]
let aRequest = Consumable(aDict: dataChange!)
tableArray.append(aRequest)
})
return tableArray
}
In my ListOfConsumablesViewController table view class
class ListOfConsumablesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private var methods:MethodsForController = MethodsForController()
private var fireBaseMethods:FireBaseMethods = FireBaseMethods()
private var consumableArray = [Consumable]()
let picker = UIImagePickerController()
#IBOutlet weak var consumableTable: UITableView!
//-------------------- VIEW DID LOAD -----------------------//
override func viewDidLoad() {
super.viewDidLoad()
//Trying to populate the table view here...
consumableArray = fireBaseMethods.populateConsumableTableArray()
consumableTable.reloadData()
self.consumableTable.dataSource = self
self.consumableTable.delegate = self
}
...
//---------------------- FUNCTIONS FOR TABLE VIEW CELLS & TABLE ----------------------//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(consumableArray.count)
return consumableArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "consumableCell", for: indexPath) as! ConsumablesCell
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.backgroundColor = UIColor.white
cell.adapterType.text = consumableArray[indexPath.row].getType()
cell.count.text = String(consumableArray[indexPath.row].getCount())
if Int(consumableArray[indexPath.row].getCount()) ?? 0 <= 0{
cell.count.textColor = UIColor.red
}else{
cell.count.textColor = UIColor.black
}
cell.sku.text = consumableArray[indexPath.row].getSku()
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
As shown below, nothing populates in the table view... I'm assuming it's something to do with the method being in a separate file, but I'm not really sure why that would be?
Simple implementation of a completion handler
//-------------- POPULATE TABLE ARRAY -----------------//
public func populateConsumableTableArray(completion: #escaping (Consumable) -> Void) {
//let the object populate itself.
ref.child("Consumables").observe(.childAdded, with: { snapshot in
guard let dataChange = snapshot.value as? [String:AnyObject] else { return }
let aRequest = Consumable(aDict: dataChange)
completion(aRequest)
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.consumableTable.dataSource = self
self.consumableTable.delegate = self
//Trying to populate the table view here...
fireBaseMethods.populateConsumableTableArray { [unowned self] consumable in
self.tableArray.append(consumable)
DispatchQueue.main.async {
self.consumableTable.reloadData()
}
}
}

I want to display data from firebase in a tableView

First Off I know this question has been asked but all the solutions I've tried hasn't worked. I want to take data from my firebase real-time database and put it into a tableView. My database is set up like this:
Clubs-
Club1-
Name- Club1
date- 5/09/18
In each tableView cell, I want to see my club name followed by the date. My code compiles fine but dosen't do anything.
My TableViewControllers Title is "Cell" but I don't have a reuse identifier on the tableViewCell.
//
// CoolTable.swift
//
//
// Created by AISD on 4/2/19.
//
import UIKit
import Firebase
import GoogleSignIn
var refa: DatabaseReference!
class CoolTable: UITableViewController{
var posts = [eventStruct]()
#IBOutlet var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
refa = Database.database().reference()
loadNews()
tableview.delegate = self
tableview.dataSource = self
}
struct eventStruct {
let name: String!
let date: String!
}
func loadNews() {
refa.child("Club").queryOrderedByKey().observe(.childAdded, with: { (snapshot) in
if let valueDictionary = snapshot.value as? [AnyHashable:String]
{
let name = valueDictionary["Name"]
let date = valueDictionary["date"]
self.posts.insert(eventStruct(name: name, date: date), at: 0)
self.tableview.reloadData()
}
})
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let label1 = cell.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].name
let label2 = cell.viewWithTag(2) as! UILabel
label2.text = posts[indexPath.row].date
return cell
}
}
You should give your cell a reuse identifier of "Cell" since that is what you're trying to get.
numberOfRowsInSection is asking for how many rows your table will have, in your case this should be posts.count.
In numberOfSections try just returning 1.
Finally, create a class for your TableViewCell to properly set the labels values. Simple explanation here.

swift: Completion handler

So, I have method loadData() which download datas from parse.com
And I should present all images show in table view.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ReusableCell", forIndexPath: indexPath) as! LeaguesTableViewCell
loadData { (success) in
if success {
cell.leagueImage.image = UIImage(data: self.leaguesImage[indexPath.row])
cell.leagueNameLabel.text = self.leagues[indexPath.row]["name"] as? String
} else {
cell.leagueNameLabel.text = "Wait"
}
}
return cell
}
Its didn't work. I call my function in viewDidLoad() but its not correct too, table view is empty.
Cuz my array is empty
My
The basic procedure for loading data into a UITableView is:
Load the data
Reload the table view
Return the number of sections in numberOfSectionsInTableView: method: In your case there is only 1 section.
Return the number of rows in tableView:numberOfRowsInSection:: In your case return the number of leagues if the data is loaded. If the data is not loaded then return 1 so that the table view has at least one row to display the "Wait" message.
Create and populate the cells from the data: Use leagues and leaguesImage.
Example:
private var loaded = false
override func viewDidLoad() {
super.viewDidLoad()
loaded = false
loadData() { success in
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.loaded = success
self.tableView.reloadData()
}
}
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection: Int) -> Int {
if loaded {
return leagues.count
}
else {
return 1
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ReusableCell", forIndexPath: indexPath) as! LeaguesTableViewCell
if loaded {
cell.leagueImage.image = UIImage(data: self.leaguesImage[indexPath.row])
cell.leagueNameLabel.text = self.leagues[indexPath.row]["name"] as? String
}
else {
cell.leagueNameLabel.text = "Wait"
}
return cell
}
Try to set delegate and datasource first. If you have separate datasource other than view controller, retain it otherwise you will not get any callback.

iOS Swift: Getting repeated value while updating 2D Array in custom UITableView cell

I have a 2D Array which I want to populate in UITableView Custom Cell in a specific pattern.
//Retrieved from Parse backend
var myArray = [["Name1", "Age1"],["Name2", "Age2"],["Name3", "Age3"]]
//What I need is:
nameArray = ["Name1", "Name2", "Name3"]
ageArray = ["Age1", "Age2", "Age3]
So that I can use indexPath to fill the Name data in the custom UITableView cell For Ex: nameArray[indexPath.row]
I tried using the for in loop,
var nameArray = NSMutableArray()
var ageArray = NSMutableArray()
//Inside CellForRowAtIndexPath
for data in myArray {
self.nameArray.addObject(data[0])
self.ageArray.addObject(data[1])
}
cell.nameLabel.text = "\(nameArray[indexPath.row])"
cell.ageLabel.text = "\(ageArray[indexPath.row])"
But I am getting repetitive name and age label filled with Name1 and Age1 in both the cell. Does anyone know whats wrong in this?
Is there a better way to reload this data as needed?
// UPDATED FULL WORKING CODE Thanks to #l00phole who helped me solve the problem
class NewViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var data = [[String]]()
var cost = Double()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
uploadData()
}
func uploadData() {
let query = PFQuery(className:"Booking")
query.getObjectInBackgroundWithId("X0aRnKMAM2") {
(orders: PFObject?, error: NSError?) -> Void in
if error == nil && orders != nil {
self.data = (orders?.objectForKey("orderDetails"))! as! [[String]]
//[["Vicky","21"],["Luke", "18"],["7253.58"]]
//*****Removing the last element as it is not needed in the tableView data
let count = self.data.count - 1
let c = self.data.removeAtIndex(count)
cost = Double(c[0])!
//******
} else {
print(error)
}
self.reloadTableData()
}
}
func reloadTableData()
{
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
return
})
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:NewTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewTableViewCell
// Configure the cell...
cell.nameLabel.text = "\(data[indexPath.row][0])"
cell.ageLabel.text = "\(data[indexPath.row][1])"
return cell
}
You are adding to the nameArray and ageArray every time cellForRowAtIndexPath is called and you are not clearing them first. This seems inefficient and you should only populate those arrays when the input data changes, not when generating the cells.
I don't even think you need those arrays, as you could just do:
cell.nameLabel.text = "\(data[indexPath.row][0])"
cell.ageLabel.text = "\(data[indexPath.row][1])"
You don't have to create separate array for name and age, you can use the existing myArray as below
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:NewTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! NewTableViewCell
// Configure the cell...
let dataArray = myArray[indexPath.row]
cell.nameLabel.text = "\(dataArray[0])"
cell.ageLabel.text = "\(dataArray[1])"
return cell
}
}

App only shows one cell (one song) as opposed to all of them

im currently writing an app but can't figure out how to translate this particular Objective-C line into swift. Any help will be much appreciated. I've been able to translate other lines just fine but this one throws some exception error. Here's the original Objective-C line:
MPMediaItem *rowItem = [songs objectAtIndex: indexPath.row];
I'm a noob and have tried many variations including:
var rowItem = songs.objectAtIndex(indexPath.row) as MPMediaItem
and
var rowItem: MPMediaItem = songs.objectAtIndex(indexPath.row) as MPMediaItem
But I've been at this so long I can't see what it should be. Many thanks!
edit:
songs is defined as:
let songsQuery = MPMediaQuery.songsQuery()
let songs = [songsQuery.items] as NSArray
i just receive an exe_breakpoint error. I can get past this if i encapsulate the MPMediaItem in an array by using ... as [MPMediaItem] but then i can't get the property values e.g. MPMediaItemPropertyTitle and MPMediaItemPropertyArtist.
update:
managed to get it working by using a for loop to obtain the MPMediaItemPropertyTitle and MPMediaPropertyArtist. The 'songs' variable is defined as i have written above. #Antonio, removing the brackets did not work. This is my current code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
let songsQuery = MPMediaQuery.songsQuery()
let allSongsArray = [songsQuery.items] as NSArray
var singleItem: AnyObject = allSongsArray[indexPath.row]
var rowItem = singleItem as [MPMediaItem]
if rowItem.count > 0 {
cell.textLabel?.text = rowItem[0].valueForProperty(MPMediaItemPropertyTitle) as NSString
cell.detailTextLabel?.text = rowItem[0].valueForProperty(MPMediaItemPropertyArtist) as NSString
}
return cell
}
The problem i have now is when i test the app, it only displays the very first song in my library and does not list every song like it is supposed to. Thank you for any help you can provide!
UPDATE 2:
Got it working with the following code:
class SongsViewController: UITableViewController {
var allSongs : [MPMediaItem] = []
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
allSongs += MPMediaQuery.songsQuery().items as [MPMediaItem]
println(allSongs.count)
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return allSongs.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
var rowItem = allSongs[indexPath.row]
cell.textLabel?.text = rowItem.valueForProperty(MPMediaItemPropertyTitle) as NSString
cell.detailTextLabel?.text = rowItem.valueForProperty(MPMediaItemPropertyArtist) as NSString
return cell
}
let allSongsArray = [songsQuery.items] as NSArray
Here you take an Array<AnyObject>! and wrap it in another Array which you then cast to an NSArray... quite a mess.
Try this:
let songsQuery = MPMediaQuery.songsQuery()
let allSongs = songsQuery.items as [MPMediaItem]
let song = allSongs[indexPath.row]
cell.textLabel?.text = song.valueForProperty(MPMediaItemPropertyTitle) as String
cell.detailTextLabel?.text = song.valueForProperty(MPMediaItemPropertyArtist) as String
Also, you really should cache allSongs instead of querying it every time you need to populate a cell. To do this, simply create add a var allSongs: [MPMediaItem] = [] to your controller. Then in for example viewDidLoad use a MPMediaQuery to populate that array. You can then use that array in any of the table view delegate and datasource methods.
I think the error you're making is loading the entire data source in cellForRowAtIndexPath, whereas that should be done in another place (viewDidLoad being a good candidate), and most important once and not for every row.
I've reworked on your code, and I've come up with this code:
class TestViewController : UITableViewController {
var allSongsArray: [MPMediaItem]!
override func viewDidLoad() {
super.viewDidLoad()
let songsQuery = MPMediaQuery.songsQuery()
self.allSongsArray = songsQuery.items as [MPMediaItem]
// Other initializations
...
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.allSongsArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
var rowItem = self.allSongsArray[indexPath.row]
cell.textLabel?.text = rowItem[0].valueForProperty(MPMediaItemPropertyTitle) as NSString
cell.detailTextLabel?.text = rowItem[0].valueForProperty(MPMediaItemPropertyArtist) as NSString
return cell
}
}
var allSongs : [MPMediaItem] = []
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
allSongs += MPMediaQuery.songsQuery().items as [MPMediaItem]
println(allSongs.count)
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return allSongs.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
// Configure the cell...
var rowItem = allSongs[indexPath.row]
cell.textLabel?.text = rowItem.valueForProperty(MPMediaItemPropertyTitle) as NSString
cell.detailTextLabel?.text = rowItem.valueForProperty(MPMediaItemPropertyArtist) as NSString
return cell
}

Resources