tableView not refreshing - ios

I am new to programming and currently building an app where I collect data from Firebase.
I thought it would be nice to implement this refreshing animation, when the view is pulled down. Now I have the problem that nothing gets refreshed at all (it was working fine in the beginning). Also every time the app launches the tableView is empty at first and when you make the pushdown it reloads, but wrong.
I am really struggling. Can anyone see anything wrong in my code?
class ViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var newsfeedTableView: UITableView!
#IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
private let refreshControl = UIRefreshControl()
var ref: DatabaseReference!
var posts = [String]()
var timeRef = [String]()
var userRef = [String]()
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
setupTableView()
ref = Database.database().reference()
ref.child("posts").observe( .childAdded, with: { snapshot in
let newPost = snapshot.value as? NSDictionary
let post_title:String = newPost!["post_title"] as? String ?? "error"
let newPostTime = snapshot.value as? NSDictionary
let post_time:String = newPostTime!["post_time"] as? String ?? "error collecting timestamp"
let newPostUser = snapshot.value as? NSDictionary
let post_user:String = newPostUser!["post_user"] as? String ?? "Team"
self.posts.insert(post_title, at: 0)
self.timeRef.insert(post_time, at: 0)
self.userRef.insert(post_user, at: 0)
self.newsfeedTableView.reloadData()
})
}
private func setupTableView() {
if #available(iOS 10.0, *) {
newsfeedTableView.refreshControl = refreshControl
} else {
newsfeedTableView.addSubview(refreshControl)
}
refreshControl.addTarget(self, action: #selector(refreshNewsFeedTableView(_:)), for: .valueChanged)
}
#objc private func refreshNewsFeedTableView(_ sender: Any) {
//self.refreshControl.beginRefreshing()
self.newsfeedTableView.reloadData()
self.refreshControl.endRefreshing()
self.activityIndicatorView.stopAnimating()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! NewsfeedCell
cell.customTextLabel?.text = posts[indexPath.row]
cell.customTimeLabel?.text = timeRef[indexPath.row]
cell.customNameLabel?.text = userRef[indexPath.row]
return cell
}
}
class NewsfeedCell: UITableViewCell {
#IBOutlet weak var customTextLabel: UILabel!
#IBOutlet weak var customNameLabel: UILabel!
#IBOutlet weak var customTimeLabel: UILabel!
}

Here
#objc private func refreshNewsFeedTableView(_ sender: Any)
you don't do any API requests to get the data then reload the table and end refreshing , and since you use observe here
ref.child("posts").observe( .childAdded, with: { snapshot in
then you'll get any new data , if you need to implement the refresh then insert all database code inside a function and replace observe with observeSingleEvent and call it from viewDidLoad and from the refresh selector method
ref.child("posts").observeSingleEvent( .value, with: { snapshot in
Note with the suggested approach you will get all the childs at a time so don't forget to clean the array inside the callback of observeSingleEvent to avoid duplicating values , also consider making one dataSource array instead of 3 here
self.posts.insert(post_title, at: 0)
self.timeRef.insert(post_time, at: 0)
self.userRef.insert(post_user, at: 0)
probably with creating a model
struct Item {
let post:String
let time:String
let user:String
}

Only problem I can see in the code is, you are using .childAdded to observe events from firebase but according to firebase document:
The child_added event is typically used when retrieving a list of
items from the database. Unlike value which returns the entire
contents of the location, child_added is triggered once for each
existing child and then again every time a new child is added to the
specified path. The event callback is passed a snapshot containing the
new child's data. For ordering purposes, it is also passed a second
argument containing the key of the previous child.
So, if you want to receive all the data of any particular node, you need to use value event but not child_added as stated here:
The value event is used to read a static snapshot of the contents
at a given database path, as they existed at the time of the read
event. It is triggered once with the initial data and again every time
the data changes. The event callback is passed a snapshot containing
all data at that location, including child data. In the code example
above, value returned all of the blog posts in your app. Everytime a
new blog post is added, the callback function will return all of the
posts.

Reload UItableView in DispatchQueue.main, try this code.
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
setupTableView()
ref = Database.database().reference()
ref.child("posts").observe( .childAdded, with: { snapshot in
let newPost = snapshot.value as? NSDictionary
let post_title:String = newPost!["post_title"] as? String ?? "error"
let newPostTime = snapshot.value as? NSDictionary
let post_time:String = newPostTime!["post_time"] as? String ?? "error collecting timestamp"
let newPostUser = snapshot.value as? NSDictionary
let post_user:String = newPostUser!["post_user"] as? String ?? "Team"
self.posts.insert(post_title, at: 0)
self.timeRef.insert(post_time, at: 0)
self.userRef.insert(post_user, at: 0)
DispatchQueue.main.async() {
self.newsfeedTableView.reloadData()
}
})
}

Related

iOS Swift 5 Using Data from Cloud Database in View Controllers in TabBarController

The problem: I cannot get data downloaded into arrays in a singleton class to populate table views in two view controllers.
I am writing a bank book iOS app with a Parse backend. I have a login viewController and four other view controllers in a Tab Bar Controller. I have a singleton class that gets data from the Parse server and loads four arrays. I want that data to populate table views in two other view controllers. I want to make as few data calls as possible. The initial view controller is where user enters debits and credits. So my plan was to call GetData class from the viewDidLoad to populate tables in case user visits them without entering a debit or a credit.
When a debit or credit is entered, there is one function where after the debit or credit is saved to Parse server, the GetData class is called again to update the arrays in the GetData class.
The two view controllers access the arrays in the GetData class to fill the tables, and there is a tableView.reloadData() call in the viewDidAppear in each view controller when the view is accessed via the tab controller.
It works intermittently at best. sometimes I get five successful updates and then it keeps displaying old data, then it will suddenly display all the data.
Looking at my cloud DB, all the entries are there when made, and I have verified the viewWillAppear is firing in each view controller who accessed.
What I need is a reliable method to get the data to update in the other view controllers every. time. I will gladly scrap this app and rewrite if needed.
Here is the code of my singleton class:
class GetData {
static let sharedInstance = GetData()
var transactionArray = [String]()
var dateArray = [String]()
var toFromArray = [String]()
var isDebitArray = [String]()
func getdata() {
let query = PFQuery(className:"Transaction")
query.findObjectsInBackground { (objects, error) in
self.transactionArray.removeAll()
self.dateArray.removeAll()
self.toFromArray.removeAll()
self.isDebitArray.removeAll()
print("query fired")
if objects != nil {
for object in objects! {
if let amount = object.object(forKey: "amount") as? String {
if let date = object.object(forKey: "date") as? String {
if let toFrom = object.object(forKey: "toFrom") as? String {
if let isDebit = object.object(forKey: "isDebit") as? String {
self.transactionArray.append(amount)
self.dateArray.append(date)
self.toFromArray.append(toFrom)
self.isDebitArray.append(isDebit)
}
}
}
}
}
}
self.transactionArray.reverse()
self.dateArray.reverse()
self.toFromArray.reverse()
self.isDebitArray.reverse()
dump(self.toFromArray)
}
}
}
Here is a sample of one of the view controllers:
class RecordVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var recordTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
recordTableView.delegate = self
recordTableView.dataSource = self
recordTableView.reloadData()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
recordTableView.reloadData()
print("recordVC viewWillAppear fired")
}
#IBAction func resetFoundButton(_ sender: Any) {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = recordTableView.dequeueReusableCell(withIdentifier: "RecordCell", for: indexPath) as! RecordCell
cell.amountLabel?.text = "$\(GetData.sharedInstance.transactionArray[indexPath.row])"
cell.dateLabel?.text = "\(GetData.sharedInstance.dateArray[indexPath.row])"
cell.toFromLabel?.text = "\(GetData.sharedInstance.toFromArray[indexPath.row])"
let cellColor = backGroundColor(isDebit: GetData.sharedInstance.isDebitArray[indexPath.row])
cell.backgroundColor = cellColor
cell.backgroundColor = cellColor
return cell
}
func backGroundColor(isDebit:String) -> UIColor{
if isDebit == "false" {
return UIColor.green
} else {
return UIColor.blue
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return GetData.sharedInstance.transactionArray.count
}
}
Thank you
I would say that instead of reloading the tables by calling tableView.reloadData() in viewWillAppear() , after your query execution and data updates in GetData Class , then you should fire a notification or use a delegate to reloadData() in tableview.
Whats happening is that sometimes when the tableView.reloadData() gets called the Data in the singleton class (GetData class) has not yet updated.
func getdata() {
let query = PFQuery(className:"Transaction")
query.findObjectsInBackground { (objects, error) in
self.transactionArray.removeAll()
self.dateArray.removeAll()
self.toFromArray.removeAll()
self.isDebitArray.removeAll()
print("query fired")
if objects != nil {
for object in objects! {
if let amount = object.object(forKey: "amount") as? String {
if let date = object.object(forKey: "date") as? String {
if let toFrom = object.object(forKey: "toFrom") as? String {
if let isDebit = object.object(forKey: "isDebit") as? String {
self.transactionArray.append(amount)
self.dateArray.append(date)
self.toFromArray.append(toFrom)
self.isDebitArray.append(isDebit)
// Here you should fire up a notification to let the 2 ViewControllers know that data has to be reloaded.
}
}
}
}
}
}
self.transactionArray.reverse()
self.dateArray.reverse()
self.toFromArray.reverse()
self.isDebitArray.reverse()
dump(self.toFromArray)
}
}

Why is my tableview showing duplicate cells after child is deleted?

i have a viewcontroller with a tableview, and when user clicks on the cell, it goes to VC2. When the user has performed a action (and updated the values in VC2), i use self.dismiss(animated: true, completion: nil) to go back to the viewcontroller with the tableview, however the tableview (once the user has gone back to the tableview) is showing duplicated rows, but the child is succesfully deleted in firebase, and a new child is created - however the tableview is showing the childs that are not deleted twice.
This is all the relevant code in VC1:
class PostMessageListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var table: UITableView!
var topicID:namePosts?
let currentUserID = Auth.auth().currentUser?.uid
var posts = [Post]()
lazy var refresher: UIRefreshControl = {
let refreshControl = UIRefreshControl()
refreshControl.tintColor = .white
refreshControl.addTarget(self, action: #selector(requestData), for: .valueChanged)
return refreshControl
}()
#objc
func requestData() {
self.table.reloadData()
refresher.endRefreshing()
}
func reloadData(){
table.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
self.table.separatorStyle = UITableViewCellSeparatorStyle.none
table.refreshControl = refresher
//DataManager.shared.firstVC = self
self.table.delegate = self
self.table.dataSource = self
let postCell = UINib(nibName: "PostTableViewCell", bundle: nil)
self.table.register(postCell, forCellReuseIdentifier: "cell")
self.posts.removeAll()
Database.database().reference().child("posts").child(postID!.name)
.observe(.childAdded) { (snap) in
if snap.exists() {
//declare some values here...
self.posts.append( //some values here)
self.posts.sort(by: {$0.createdAt > $1.createdAt})
self.table.reloadData()
})
}
else {
self.table.reloadData()
}
}
//observe if a post is deleted by user
Database.database().reference().child("posts").child("posts").observe(.childRemoved) { (snapshot) in
let postToDelete = self.indexOfPosts(snapshot: snapshot)
self.posts.remove(at: postToDelete)
self.table.reloadData()
//self.table.deleteRows(at: [NSIndexPath(row: questionToDelete, section: 1) as IndexPath], with: UITableViewRowAnimation.automatic)
//self.posts.remove(at: indexPath.row)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.posts.count
}
func indexOfPosts(snapshot: DataSnapshot) -> Int {
var index = 0
for post in self.posts {
if (snapshot.key == post.postID) {
return index
}
index += 1
}
return -1
}
EDIT: Forgot to say, but i have used this code in another Viewcontroller, and it works fine there. However i just copied my code from that to this one, and deleted a bunch of stuff i didn't need, however i cant seem to find what i am missing in this one.
This may not be the answer but it may lead to an answer. As noted in the comments there are two arrays being used to manage the dataSource for the tableView. One contains the data and one is using an indexing technique - I believe that may lead to issues, like the one described in the question.
The other issue is that when every child is intially added, we re-sort the array and then refresh the tableView - that can lead to delays and flicker. (flicker = bad)
So let establish a couple of things. First a class that holds the posts
PostClass {
var post_id = ""
var post_text = ""
var creation_date = ""
}
second the Firebase structure, which is similar
posts
post_id_0
text: "the first post"
timestamp: "20190220"
post_id_1
text: "the second post"
timestamp: "20190221"
then a little trick to populate the datasource and leave a child added observer. This is important as you don't want to keep refreshing the tableView with every child it as may (will) flicker. So we leverage that childAdded events always come before .value events so the array will populate, and then .value will refresh it once, and then we will update the tableView each time after. Here's some code - there's a lot going on so step through it.
var postsArray = [String]()
var initialLoad = true
func ReadPosts() {
let postsRef = self.ref.child("posts").queryOrdered(byChild: "timestamp")
postsRef.observe(.childAdded, with: { snapshot in
let aPost = PostClass()
aPost.post_id = snapshot.key
aPost.post_text = snapshot.childSnapshot("text").value as! String
aPost.creation_date = snapshot.childSnapshot("timestamp").value as! String
self.postsArray.append(aPost)
//upon first load, don't reload the tableView until all children are loaded
if ( self.initialLoad == false ) {
self.postsTableView.reloadData()
}
})
//when a child is removed, the event will contain that child snapshot
// we locate the child node via it's key within the array and remove it
// then reload the tableView
postsRef.observe(.childRemoved, with: { snapshot in
let keyToRemove = snapshot.key
let i = self.postsArray.index(where: { $0.post_id == keyToRemove})
self.postsArray.remove(at: i)
self.postsTableView.reloadData()
})
//this event will fire *after* all of the child nodes were loaded
// in the .childAdded observer. So children are sorted, added and then
// the tableView is refreshed. Set initialLoad to false so the next childAdded
// after the initial load will refresh accordingly.
postsRef.observeSingleEvent(of: .value, with: { snapshot in
self.postsTableView.reloadData()
self.initialLoad = false
})
}
Things to note
We are letting Firebase doing the heavy lifting and ordering the nodes by creation_date so they come in order.
This would be called from say, viewDidLoad, where we would set the initialLoad class var to true initially

How to observe more levels from realtime-database Firebase?

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

Removing rows from Tableview after time expires

I have searched every source I know of for help on this problem. I want to make individual rows within a tableview disappear after a certain amount of time expires. Even if the app is not open, I want the rows to delete as soon as the timer reaches zero. I have been trying to arrange each post into an dictionary with timer pairings to handle the row deletion when time occurs. I have looked at this post for guidance but no solutions. Swift deleting table view cell when timer expires.
This is my code for handling the tableview and the timers:
var nextID: String?
var postsInFeed = [String]()
var postTimer = [Timer: String]()
var timeLeft = [String: Int]()
(in view did load)
DataService.ds.REF_FEED.observe(.value, with: { (snapshot) in
self.posts = []
self.postsInFeed = []
self.nextID = nil
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] { //Gets us into our users class of our DB
for snap in snapshot { // Iterates over each user
if let postDict = snap.value as? Dictionary<String, Any> { // Opens up the dictonary key value pairs for each user.
let key = snap.key //
let post = Post(postID: key, postData: postDict) // Sets the properties in the dictionary to a variable.
self.posts.append(post) // appends each post which contains a caption, imageurl and likes to the empty posts array.
self.nextID = snap.key
let activeID = self.nextID
self.postsInFeed.append(activeID!)
print(self.postsInFeed)
print(activeID!)
}
}
}
self.tableView.reloadData()
})
//////////////////////////////////////////////////////////////////////////////////////////////
// Sets up our tableview
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postsInFeed.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let post = posts[indexPath.row] // We get our post object from the array we populate when we call the data from the database up above.
if let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell") as? TableViewCell { //Specifies the format of the cell we want from the UI
// cell.cellID = self.postsInFeed[indexPath.row]
cell.cellID = self.postsInFeed[indexPath.row]
cell.homeVC = self
if let img = HomeVC.imageCache.object(forKey: post.imageUrl as NSString){
cell.configureCell(post: post, img: img as? UIImage)
print(postTimer)
print(self.timeLeft)
} else {
cell.configureCell(post: post)
print(postTimer)
print(self.timeLeft)
}
return cell
} else {
return TableViewCell()
}
}
func handleCountdown(timer: Timer) {
let cellID = postTimer[timer]
// find the current row corresponding to the cellID
let row = postsInFeed.index(of: cellID!)
// decrement time left
let timeRemaining = timeLeft[(cellID!)]! - 1
timeLeft[cellID!] = timeRemaining
if timeRemaining == 0 {
timer.invalidate()
postTimer[timer] = nil
postsInFeed.remove(at: row!)
tableView.deleteRows(at: [IndexPath(row: row!, section: 0)], with: .fade)
} else {
tableView.reloadRows(at: [IndexPath(row: row!, section: 0)], with: .fade)
}
}
In the tableviewcell:
weak var homeVC: HomeVC?
var cellID: String!
func callTime() {
homeVC?.timeLeft[cellID] = 25
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(homeVC?.handleCountdown(timer:)), userInfo: nil, repeats: true)
homeVC?.postTimer[timer] = cellID
}
Any help would be really appreciated!
Timers don't run when your app is not running or is suspended.
You should rework your code to save a date (to UserDefaults) when you start your timer, and then each time the timer fires, compare the current date to the saved date. Use the amount of elapsed time to decide how many entries in your table view's data model to delete. (In case more than one timer period elapsed while you were suspended/not running.)
You should implement applicationDidEnterBackground() in your app delegate (or subscribe to the equivalent notification, UIApplicationDidEnterBackground) and stop your timer(s).
Then implement the app delegate applicationDidBecomeActive() method (or add a notification handler for the UIApplicationDidBecomeActive notification), and in that method, check the amount of time that has elapsed, update your table view's data model, and tell the table view to reload if you've removed any entries. Then finally restart your timer to update your table view while your app is running in the foreground.

Data does not show up in table view cell when i run my code

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.

Resources