I am trying to populate a UITableView using an array and I am unable to do so. Here is what I have so far. This code is for retrieving data and storing it in the array that I am using to populate the UITableView:
func prepareForRetrieval() {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).observe(.value, with: {
(snapshot) in
for snap in snapshot.children.allObjects {
let id = snap as! DataSnapshot
self.keyArray.append(id.key)
}
self.updateCart()
})
}
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
print(self.cartArray.count)
})
}
}
The data is properly appending into the array and when I print the count of the array, it prints the correct count. This means that the data is there. However, when I try to populate a UITableView, it doesn't detect any data. I have the following code to make sure that there is data in the array before trying to populate the UITableView:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.prepareForRetrieval()
if cartBrain.cartArray.isEmpty == false{
tableViewOutlet.dataSource = self
tableViewOutlet.reloadData()
}
else {
tableViewOutlet.isHidden = true
tableViewOutlet.isUserInteractionEnabled = false
purchaseButtonOutlet.isEnabled = false
cartEmptyLabel.text = "Your cart is empty. Please add items and check back later."
}
}
When I open the View Controller, the TableView is disabled because it doesn't detect any data. I have already set the data source to self and the thing is that when the count of the array is printed, it again prints the correct amount. I have already set the data source to self for the UITableView. Here is my code for the UITableView:
extension CartViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cartBrain.cartArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cartcustomcell", for: indexPath)
cell.textLabel?.text = cartBrain.cartArray[indexPath.row].itemName
cell.detailTextLabel?.text = String(cartBrain.cartArray[indexPath.row].itemQuantity)
return cell
}
}
I don't understand why the count of the array prints the correct amount meaning that there is data stored in it but when the View Controller is loaded, it detects that the array is empty. Thanks for the help and I'm sorry if the question is a bit unclear.
After appending data to cartArray in updateCart you should reloadData(), like this:
weak var tableViewOutlet: UITableView?
func updateCart() {
for key in keyArray {
Database.database().reference().child("UserCart").child(Auth.auth().currentUser!.uid).child(key).observeSingleEvent(of: .value, with: {
(snapshot) in
let value = snapshot.value as? NSDictionary
let itemName = value?["Item Name"] as! String
let itemPrice = value?["Item Price"] as! Float
let itemQuantity = value?["Item Quantity"] as! Int
self.cartArray.append(CartData(itemName: itemName, itemQuantity: itemQuantity, itemPriceNumber: itemPrice))
DispatchQueue.main.async {
self.tableViewOutlet.reloadData()
}
})
}
}
The updateCart doesn't seem to have any connection to the tableViewOutlet so you need to pass in a reference to it in your viewDidLoad like this:
override func viewDidLoad() {
super.viewDidLoad()
cartBrain.tableViewOutlet = tableViewOutlet
cartBrain.prepareForRetrieval()
Note: Since you're using a for loop to trigger the async call multiple times you can use the array count to check if all the items are appended to do the reload to avoid multiple reloads.
Related
I am using Firebase to populate a TableView in my iOS app. The first few objects are loaded but once I get to the third item in my list the app crashes with the exception:
'NSRangeException', reason: '*** __boundsFail: index 3 beyond bounds [0 .. 2]'
I know that this means that I am referring to an array at an index that it does not contain however I do not know why.
I create the TableView with a TableViewController and initialize it like so:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(posts.count)
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = posts[indexPath.row]
print(post)
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! PostCell
let firstReference = storageRef.child(post.firstImageUrl)
let secondReference = storageRef.child(post.secondImageUrl)
cell.firstTitle.setTitle(post.firstTitle, for: .normal)
cell.secondTitle.setTitle(post.secondTitle, for: .normal)
cell.firstImageView.sd_setImage(with: firstReference)
cell.secondImageView.sd_setImage(with: secondReference)
// Configure the cell...
return cell
}
I believe that the first function creates an array with the number of objects in posts and that the second function assigns values to the template for the cell. The print statement in the first method prints 4 which is the correct number of objects retrieved from firebase. I assume that means an array is created with 4 objects to be displayed in the TableView. This is what is really confusing because the error states that there are only 3 objects in the array. Am I misunderstanding how the TableView is instantiated?
Here is the code that fills the TableView:
func loadMessages(){
db.collectionGroup("userPosts")
.addSnapshotListener { (querySnapshot, error) in
self.posts = []
if let e = error{
print("An error occured trying to get documents. \(e)")
}else{
if let snapshotDocuments = querySnapshot?.documents{
for doc in snapshotDocuments{
let data = doc.data()
if let firstImage = data[K.FStore.firstImageField] as? String,
let firstTitle = data[K.FStore.firstTitleField] as? String,
let secondImage = data[K.FStore.secondImageField] as? String,
let secondTitle = data[K.FStore.secondTitleField] as? String{
let post = Post(firstImageUrl: firstImage, secondImageUrl: secondImage, firstTitle: firstTitle, secondTitle: secondTitle)
self.posts.insert(post, at: 0)
print("Posts: ")
print(self.posts.capacity)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
The app builds and runs and displays the first few items but crashes once I scroll to the bottom of the list. Any help is greatly appreciated.
Edit:
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)
loadMessages()
}
You're getting an out-of-bounds error because you're dangerously populating the datasource. You have to remember that a table view is constantly adding and removing cells as it scrolls which makes updating its datasource a sensitive task. You reload the table on each document iteration and insert a new element in the datasource at index 0. Any scrolling during an update will throw an out-of-bounds error.
Therefore, populate a temporary datasource and hand that off to the actual datasource when it's ready (and then immediately reload the table, leaving no space in between an altered datasource and an active scroll fetching from that datasource).
private var posts = [Post]()
private let q = DispatchQueue(label: "userPosts") // serial queue
private func loadMessages() {
db.collectionGroup("userPosts").addSnapshotListener { [weak self] (snapshot, error) in
self?.q.async { // go into the background (and in serial)
guard let snapshot = snapshot else {
if let error = error {
print(error)
}
return
}
var postsTemp = [Post]() // setup temp collection
for doc in snapshot.documents {
if let firstImage = doc.get(K.FStore.firstImageField) as? String,
let firstTitle = doc.get(K.FStore.firstTitleField) as? String,
let secondImage = doc.get(K.FStore.secondImageField) as? String,
let secondTitle = doc.get(K.FStore.secondTitleField) as? String {
let post = Post(firstImageUrl: firstImage, secondImageUrl: secondImage, firstTitle: firstTitle, secondTitle: secondTitle)
postsTemp.insert(post, at: 0) // populate temp
}
}
DispatchQueue.main.async { // hop back onto the main queue
self?.posts = postsTemp // hand temp off (replace or append)
self?.tableView.reloadData() // reload
}
}
}
}
Beyond this, I would handle this in the background (Firestore returns on the main queue) and only reload the table if the datasource was modified.
After some fiddling around and implementing #bsod's response I was able to get my project running. The solution was in Main.Storyboard under the Attributes inspector I had to set the content to Dynamic Prototypes.
My code do not add values from a while statement of a UIViewController to an Array of a UITableViewController.
This is for a getter function to allow me to see all childrens values under other childrens. Now I'm going to be more specific:
My database node is made of:
Cars -> 0, 1, 2, 3, ... -> Model, Price, ... -> String
As you can see, The number of childs is undefined, so I have to use this control method:
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
}
First of all, In a loading ViewController, I get code node keys of cars and save them to cars variable of type NSMutableArray of the TableViewController. Then I will do the same thing in the TableViewController to get all indexpath.row childrens value.
let rootRef = Database.database().reference()
let carconditionalRef = rootRef.child("Cars")
carconditionalRef.observe(.value) {(snap: DataSnapshot) in
//Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
//Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
let carvc = Cars_Table();
carvc.cars.add(child.key)
}
}
It results that with this code I still have empty NSMutableArray. How can I solve this?
Edit 1
I fixed that snippet to this:
import UIKit
import FirebaseDatabase
class Loading: UIViewController {
#IBOutlet weak var loading: UIActivityIndicatorView!
var mother: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
start()
}
func start() {
loading.startAnimating()
if #available(iOS 10.0, *) {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
//let's dance
self.loading.startAnimating()
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self.mother.add(child.key)
}
self.move()
self.loading.stopAnimating()
self.performSegue(withIdentifier: "loadingfinish", sender: nil)
}
}
} else {
// Fallback on earlier versions
}
}
func move() {
let vc = Cars_Table()
vc.cars = self.mother
}
}
Edit 2
I tried using the recursive method, but it did not work. So I tried one more time with the iterative method this time using the while statement.
Here my new function, this time directly in the Car_TableView.swift:
func loadData() {
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self.populateTable.append(child.key)
}
var counter = 0
while counter > -self.populateTable.count {
counter -= 1
let rootRef = Database.database().reference()
let userRef = rootRef.child("Cars").child("\(self.populateTable.count+counter)")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let model1 = userDict["Model"] as! String
self.model.add(model1)
let detail1 = userDict["Detail"] as! String
self.detailpage.add(detail1)
let year1 = userDict["Year"] as! String
self.year.add(year1)
let carPrice1 = userDict["Price"] as! String
self.price.add(carPrice1)
let carimageURL1 = userDict["imageURL"] as! String
self.imagePathString.add(carimageURL1)
}) //end observeSingleEvent
}
}
}
When I go to do the while, the observeSingleEvent will be work, but it will repeat n^2 times. Why does this happen?
Edit 3
Since the problem seems to be changed since the start, I edited to give all the relevant details. So, the problem now is different and now are two:
When I load database I have n^2 repeated instruction
To see the table filled with database data, I have to touch the tab bar button to the next ViewController then touch the tab bar button to come back on Car_TableView.swift
For the first problem... onestly I have no idea why this happens 😅
For the second problem I thought to use SVProgressHUD to reload data but it doesn't work on loadData() function and if I try the Instance Method tableView.reloadData() it crashes.
variables are all NSMutableArray since that I have to load a lot of stuff that can change in the time
My viewDidLoad() function is very easy as you can see:
override func viewDidLoad() {
super.viewDidLoad()
loadData()
}
This is my Table view data source in our Car_TableView.swift:
override func numberOfSections(in tableView: UITableView) -> Int {
return populateTable.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return populateTable.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "carTableCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! Car_Cell
cell.carLabel?.text = "\(self.model[indexPath.row])"
cell.carSubtitle?.text = "Year: \(self.year[indexPath.row]) - Price: \(self.price[indexPath.row])$"
Alamofire.request("\(self.imagePathString[indexPath.row])").response { response in
guard let image = UIImage(data:response.data!) else {
// Handle error
return
}
let imageData = image.jpegData(compressionQuality: 1.0)
cell.carImage.contentMode = .scaleAspectFit
cell.carImage.image = UIImage(data : imageData!)
}
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowcarDetails" {
let myIndexPath = self.tableView.indexPathForSelectedRow!
//save detail page url in UserDefault
let SVDetail = self.detailpage[myIndexPath.row]
let SVDetaildefaults = UserDefaults.standard
SVDetaildefaults.set(SVDetail, forKey: "sv_detail")
SVDetaildefaults.synchronize()
_ = segue.destination
as! Car_Detail
}
}
//SET CELLS SIZE
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.row {
case 0,1,2,3,4:
return 100
default:
return 100
}
}
I also re-post the loadData() function because I simplified the operation in an only while statement:
func loadData() {
//call data from database
let rootRef = Database.database().reference()
let conditionalRef = rootRef.child("Cars")
conditionalRef.observe(.value) {(snap: DataSnapshot) in
// Get all the children from snapshot you got back from Firebase
let snapshotChildren = snap.children
// Loop over all children (code) in Firebase
while let child = snapshotChildren.nextObject() as? DataSnapshot {
// Get code node key and save it to cars array
self. populateTable.append(child.key)
let userRef = rootRef.child("Cars").child("\(child.key)")
userRef.observeSingleEvent(of: .value, with: { snapshot in
let userDict = snapshot.value as! [String: Any]
let address1 = userDict["Address"] as! String
self.address.add(address1)
let detail1 = userDict["Detail"] as! String
self.detailpage.add(detail1)
let carnumberOfRooms1 = userDict["numberOfRooms"] as! String
self.numberOfRooms.add(carnumberOfRooms1)
let carPrice1 = userDict["Price"] as! String
self.price.add(carPrice1)
let carimageURL1 = userDict["imageURL"] as! String
self.imagePathString.add(carimageURL1)
}) //end observeSingleEvent
} //end while
} //end snap
}//end func
This is my code —- I am getting error when returning cell1 inside the if statement as it says ” Cannot return a non void return value in void function.I want to return the cell in tableview .. and i have 3 kind of posts .. one for status one for image one for video post. How can i return the cell for each.
P.S. : I have just provided the code for one post type only as if one is solved then all other can be solved.
import UIKit
import Alamofire
class ViewController: UIViewController , UITableViewDelegate ,
UITableViewDataSource{
#IBOutlet weak var feedTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
feedTable.dataSource = self
feedTable.delegate = self
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 376
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
Alamofire.request("https://new.postpfgsdfdsgshfghjoves.com/api/posts/get_all_posts").responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String,AnyObject> {
if let successcode = dict["STATUS_CODE"] as? Int {
if successcode == 1 {
if let postsArray = dict["posts"] as? [Dictionary<String,AnyObject>]
{
for i in 0..<postsArray.count
{
let posttype = postsArray[i]["media_type"] as! String
if posttype == "image"
{
let cell1 : ImageTableViewCell = self.feedTable.dequeueReusableCell(withIdentifier: "imageReuse") as! ImageTableViewCell
cell1.fullName = postsArray[i]["full_name"] as? String
cell1.profileImageURL = postsArray[i]["profile_pic"] as? String
cell1.location = postsArray[i]["location"] as? String
cell1.title = postsArray[i]["title"] as? String
cell1.postTime = postsArray[i]["order_by_date"] as? String
cell1.likes = postsArray[i]["liked_count"] as? Int
cell1.comments = postsArray[i]["comment_count"] as? Int
cell1.imageURL = postsArray[i]["profile_pic"] as? String
cell1.imageLocation = postsArray[i]["location"] as? String
cell1.content = postsArray[i]["content"] as? String
cell1.profileFullName.text = cell1.fullName
cell1.titleImagePost.text = cell1.title
cell1.postLocation.text = cell1.location
cell1.profileUserLocation.text = cell1.location
cell1.numberOfLikes.text = "\(cell1.likes!) Likes"
cell1.numberOfComments.text = "\(cell1.comments!) Comments"
cell1.postTimeOutlet.text = postsArray[i]["posted_on"] as? String
let url = URL(string: cell1.imageURL!)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
cell1.profileImage.image = UIImage(data: data!)
let url1 = URL(string: cell1.imageURL!)
let data1 = try? Data(contentsOf: url1!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
cell1.postedImage.image = UIImage(data: data1!)
// return cell1
}
else if posttype == "status"
{
let cell1 : StatusTableViewCell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "statusReuse") as! StatusTableViewCell
cell1.fullName = postsArray[i]["full_name"] as? String
cell1.profileImageURL = postsArray[i]["profile_pic"] as? String
cell1.location = postsArray[i]["location"] as? String
cell1.title = postsArray[i]["title"] as? String
cell1.postTime = postsArray[i]["order_by_date"] as? String
cell1.likes = postsArray[i]["liked_count"] as? Int
cell1.comments = postsArray[i]["comment_count"] as? Int
cell1.postContent = postsArray[i]["content"] as? String
cell1.profileFullName.text = cell1.fullName
cell1.titleStatusPost.text = cell1.title
cell1.postLocation.text = cell1.location
cell1.profileUserLocation.text = cell1.location
cell1.content.text = cell1.postContent
cell1.numberOfLikes.text = "\(cell1.likes!) Likes"
cell1.numberOfComments.text = "\(cell1.comments!) Comments"
cell1.postTimeOutlet.text = "\(cell1.postTime!)"
let url = URL(string: cell1.profileImageURL!)
let data = try? Data(contentsOf: url!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
cell1.profileImage.image = UIImage(data: data!)
// return cell1
}
else if posttype == "video"
{
let cell1 : VideoTableViewCell = self.feedTable.dequeueReusableCell(withIdentifier: "videoReuse") as! VideoTableViewCell
cell1.fullName = postsArray[i]["full_name"] as? String
// cell1.profession = postsArray[i]["profession"] as? String
cell1.profileImageURL = postsArray[i]["profile_pic"] as? String
cell1.location = postsArray[i]["location"] as? String
cell1.title = postsArray[i]["title"] as? String
cell1.postTime = postsArray[i]["order_by_date"] as? String
cell1.likes = postsArray[i]["liked_count"] as? Int
cell1.comments = postsArray[i]["comment_count"] as? Int
cell1.videoURL = postsArray[i]["profile_pic"] as? String
cell1.profileFullName.text = cell1.fullName
cell1.titleVideoPost.text = cell1.title
cell1.postLocation.text = cell1.location
cell1.profileUserLocation.text = cell1.location
// return cell1
}
}
}
}
}
}
}
}
}
My answer isn't any different from the others but let me be a little more specific. I'll use a generic example and you'll need to tailor this to your specific needs.
1) Define a model somewhere for your data such as:
class MyDataItem {
var name: String
var title: String
var location: String
init(name: String, title: String, location: String) {
self.name = name
self.title = title
self.location = location
}
}
2) Define an array in your Viewcontroller such as:
var dataArray = [MyDataItem]()
3) Load the data which you could do from the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
feedTable.dataSource = self
feedTable.delegate = self
loadData()
}
4) Implement loadData() function:
func loadData() {
// Here put in your alamo enclosure to retrieve the data and store it into the array you've defined
// When done, call reload data
feedTable.reloadData()
}
5) Your cellForRowAt function will need to be modified to retrieve the data from the array. For example:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell1 : ImageTableViewCell = tableView.dequeueReusableCell(withIdentifier: "imageReuse") as! ImageTableViewCell
cell1.fullName = dataArray[indexPath.row].name
cell1.title = dataArray[indexPath.row].title
cell1.location = dataArray[indexPath.row].location
return cell1
}
Anyway, this is the general idea on how to do what you are attempting. When reloadData is called from your loadData function, it will cause the tableview to reload from the array data correctly.
Hope this helps!
The problem is you do not return the cell, you simply make some async request with alamofire and return an instance of the cell from the closure.
func foo() -> Int { return 1 } ≠ func bar() -> Int { someClosure { return 1 } }
Firstly you need load the the data from https://www.example.com/api/posts/get_all_posts into some data model.
var models: [SomeTypeYouCreate] = []
func loadData() {
Alamofire.request(...).responseJSON { response in
self.models = /* Create array of `SomeTypeYouCreate` objects from response */
self.tableView.reloadData()
}
}
func func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let model = self.models[indexPath.row]
// configure cell with model
return cell
}
You cannot do it the way you're trying to. You're not returning a cell from cellForRowAt method, you're returning it in Alamofire callback closure. What you should do is to return the cell in your cellForRowAt method, and implement some sort of setup method for your UITableViewCell subclass and make your calls in there
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell1 : ImageTableViewCell = self.feedTable.dequeueReusableCell(withIdentifier: "imageReuse") as! ImageTableViewCell
// put your Alamofire code inside such function in your UITableViewCell subclass
cell.setup()
return cell
}
First and foremost, you are returning value in closure Alamofire.request. If you wanna use cell after you confirm cell values, you want to pass over completion handler to the function and use it in that Alamofire.reqeust...
But if I were you, I would create another function which is called before/after tableView function.
If it is Before then trigger tableview initialization upon alamofire completion.
If it is After then reload when values are loaded correctly in Alamofire.
EDITED:
Like other suggested,it is bad idea to load data in tableView function. Also, by using Alamofire, it means you use Closure. That is, whatever you wanna do in Alamofire happens asynchronously, meaning by the time what you want to achieve in Alamofire is done, your program can be out of the table view function. Also, since it is closure, returning value in Alamofire does not satisfy your tableView return type.
So basically, if you need data via API and verify, you declare function such that do whatever you doing Alamofire and then reload the tableView.
So flow is like this:
1) Make an empty array and put array.count to # of rows.
2) Since it is empty, when tableView first try to generate cells, it doesn't do anything.
3) You call the function which uses Alamofire. If returned values are good, then add the cell(model) to the array.
4) After you are done loading models, do tableView.reload().
5) Tableview calls tableView function now it finds value in array so that will create cells.
I'm new to swift and having trouble with setting value to FIRDataSnapshot. I simply created a calculator. I took MealCaloryArray in didload method and display it tableviewcell (with the help of FIRDataSnapshot list) and the calory is changed in the plus function and I have to send the new value to tableviewcell again. However, I couldnt set the new value in FIRDataSnapshot array list. I tried to useself.calory[buttonRow].setValue(<value: n, forUndefinedKey:"")but I dont have proper "forUndefinedKey" value. Do you have any suggestions?
My nested Firebase DB Structure and code blocks is attached.
Firebase Child Structure:
Database Screenshot
var calory: [FIRDataSnapshot]! = []
override func viewDidLoad() {
ref = FIRDatabase.database().reference()
let CoursesRef = ref.child("CompanyMeals")
CoursesRef.observe(.childAdded, with: { snapshot in
self.calory = snapshot.childSnapshot(forPath: "MealCaloryArray").children.allObjects as! [FIRDataSnapshot]
self.calory.append(snapshot)
self.ingredientTableView.reloadData()
})
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ingredientTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MealCalculatorListCell
let cellcalory = self.calory[indexPath.row].value as? Double
if (cellcalory != nil) {
cell.itemTotalCalory.text = ("\(cellcalory!)")//String (describing: cellcalory)
let cellcalory1 = Int(cellcalory!)
firstCaloriesArray.append((cellcalory1 as AnyObject))
}
}
#IBAction func plusAction(sender: UIButton) {
cell.itemTotalCalory.text = String ((Int(oldcalory!) + fcCalory))
let newcalory = cell.itemTotalCalory.text
let n = String(newcalory!)
self.calory[buttonRow].setValue(value: n, forUndefinedKey: "")
}
Please help! I've tried everything. If anyone has any advice on how i can display my data in the table view cell, I would be eternally grateful. I'm new to iOS and am learning on a very steep pace. I grabbed data from an API that returned data in the form of JSON, parsed it, created my table view with its table view cells, but i can't seem to figure out how to print the data i parsed through in the table view cell.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.viacom.com/apiKey=someKey")!
let request = NSMutableURLRequest(URL: url)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
var json: [String: AnyObject]!
do {
json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions()) as! [String : AnyObject]
} catch {
print(error)
}
//2 - Store in model, forloop through them, store into temparray,add to main array?
let episodes = json["response"] as! [String: AnyObject]
let meta = episodes["episodes"] as! [AnyObject]
let description = meta[2]["description"]! as! String?
//let title = meta[2]["title"] as! String?
let episodeNumber = meta[2]["episodeNumber"]! as! String?
dispatch_async(dispatch_get_main_queue(), {
self.myTableView.reloadData()})
data = [episodeNumber!, description!]
print("Episode Number: \(episodeNumber!)\n" + "Description: \(description!)")
} else {
print(error)
}
}
task.resume()
}
let data = [description]
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel!.text = "\(self.data)"
return cell
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Your codes look very messy to me. However, I'm just assuming that you have successfully fetched the JSON data. Fetching data is asynchronous. You therefore need to add a dispatch code inside.
After your this line of code:
let episodeNumber = meta[2]["episodeNumber"]! as! String?
Add this
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()})
EDIT:
#IBOutlet weak var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self // Add This
}
}
The reason for the failure is too much of data manipulation. There is no need to use so many variables and pass around data unnecessarily. You are getting correct output in console when printing it because you used variables "episodeNumber" and "description".
print("Episode Number: \(episodeNumber!)\n" + "Description: \(description!)")
And getting wrong data in variable "data".
So better thing would be that you should use episodeNumber and description variables to print data in Cell.
cell.textLabel!.text = "Episode Number: \(self.episodeNumber)\n" + "Description: \(description)"
But for this you have to make variable episodeNumber a global variable.
So declare it outside the function.
var episodeNumber = String()
and remove the let keyword from line
let episodeNumber = meta[2]["episodeNumber"]! as! String?
You have to add some self. keywords which the compiler will suggest you so you don't have to worry about that, just keep on double clicking the suggestions.
Now, your code looks fine to run and get desired output.
let data = [description]
is a short form of
let data = [self.description]
and self.description() is the viewController's description method used for printing debug description. That is why
cell.textLabel!.text = "\(self.data)"
gives you [(Function)], as you just created an array with a stored function in it.