how can I refresh data in a collection view in swift 4? - ios

I have a function to refresh the data of an array every 2 seconds
var timer = Timer()
func timeRefresh(){
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(ViewController.refreshData), userInfo: nil, repeats: true)
}
I receive the data from a post service
#objc func refreshData(){
post(postString, Route) { (res) in
let success = res["success"]
if success == true {
let walkers = res["walkers"]
for secondItem in walkers.array! {
let duration = secondItem["duration"]
self.timeCar.append(duration.stringValue) //this is the info that i need for the collection view
}
}
print("array time \(self.timeCar)")
}else{
self.timeCar = ["-.-","-.-","-.-","-.-","-.-"]
}
}
}
I need refresh the collection view every second
cell.typeLabel.text = timeCar[indexPath.row]

UICollectionView has multiple ways of reloading data.
If you want every cell to be reloaded:
collectionView.reloadData().
If you want a specific section to be reloaded: collectionView.reloadSections(<Array of Sections>).
If you want to reload specific cells: collectionView.reloadItems(<Array of IndexPaths>).

Related

Looking for best practices when updating a tableview by monitoring for changes in DB

I'm somewhat new to this and this is my first question on stackoverflow. Thanks in advance for your help and bear with me if my formatting sucks
I've got multiple views within my app (all displaying data using tableview subviews) that need to update automatically when the data changes on the database (Firestore), i.e. another user updates the data.
I've found a way to do this which is working well, but I want to ask the community if there's a better way.
Currently, I am creating a Timer object with a timeInterval of 2. On the interval, the timer queries the database and checks a stored data sample against updated data. If the two values vary, I run viewDidLoad which contains my original query, tableView.reloadData(), etc..
Any suggestions or affirmations would be very useful.
var timer = Timer()
var oldChallengesArray = [String]()
var newChallengesArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
//set tableview delegate
mainTableView.delegate = self
mainTableView.dataSource = self
//set challengesmodel delegate
challengesModel.delegate = self
//get challenges
DispatchQueue.main.async {
self.challengesModel.getChallenges(accepted: true, challengeDenied: false, incomingChallenges: false, matchOver: false)
self.mainTableView.reloadData()
}
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 1 seconds
timer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(self.updateTableView), userInfo: nil, repeats: true)
}
#objc func updateTableView(){
ChallengeService.getAllUserChallengeIDs(accepted: true, challengeDenied: false, matchOver: false) { (array) in
if array.isEmpty {
return
} else {
self.newChallengesArray = array
if self.oldChallengesArray != self.newChallengesArray {
self.oldChallengesArray = self.newChallengesArray
self.newChallengesArray.removeAll()
self.viewDidLoad()
}
}
}
}
Firestore is a "realtime database", that means that the database warns you when changes happen to the data. To achieve that the app needs to subscribe to relevant changes in the db. The sample code below can be found here:
db.collection("cities").document("SF")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
print("Current data: \(data)")
}
Also, I would like to point out that calling viewDidLoad is incorrect, you should never call viewDidLoad yourself, create an func to update the data. Something like this:
DispatchQueue.main.async {
self.mainTableView.reloadData()
}

Fail to change the label text with a timer in swift

As a swift beginner, I'm building a simple app which will get data from a website to update the label text.
In the ViewController.swift I begin with a function called requestCycle():
override func viewDidLoad() {
super.viewDidLoad()
requestCycle()
}
In this requestCycle() function I create a timer to call the http request function basicAuthHttpRequest():
func requestCycle(){
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(ViewController.basicAuthHttpRequest), userInfo: nil, repeats: true);
RunLoop.current.add(self.timer, forMode: RunLoopMode.commonModes);
}
In the basicAuthHttpRequest() function, I set up an http request to get data from a url, and use the data to update the label text:
...
//http request, parse json, store the data in TempIn
let TempInString = String(describing: TempIn!)
self.TempInLabel.text = TempInString
print(TempInString)
print(self.TempInLabel.text!)
...
When I run the app, the data will be printed("33" and Optional("33")) and NO ERRORs occur. However, the text of the label shown is not changed at all.
If I use a button to trigger the function basicAuthHttpRequest(), after clicking the button, the label text will be changed in a few seconds.
What's wrong with my poor timer? =.=
You have to update text on the main thread.
DispatchQueue.main.async {
let TempInString:String = String(describing: TempIn!)
self.TempInLabel.text = TempInString as String
}
Try below code :-
DispatchQueue.main.async(execute: { self.TempInLabel.text = TempInString as String })
The timer is running on background thread. so you have to update label value with main thread. You have to use below code for the same.
self.performSelector(onMainThread: #selector(updateUI), with: nil, waitUntilDone: true)
func updateUI() {
print("here update your UI")
}
Also you can do with OperationQueue.
OperationQueue.main.addOperation({
print("here update your UI")
})

Swift : 3.1 UILabel text not updating in timer (Call every second)

I have created one timer object and set #selector method, In #selector method my label update every time that display timer count down value, but when I push or pop another view controller and come back to timer view controller my label not updating timer count down value
override func viewDidLoad() {
super.viewDidLoad()
if timer == nil {
self.startTimer()
}
}
func startTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(timeInterval:1, target: self, selector: #selector(self.update), userInfo: nil, repeats: true);
}
func update() {
count += 1
if(count > 0){
let ti = NSInteger(count)
let strSeconds = ti % 60
let strMinutes = (ti / 60) % 60
let strHours = (ti / 3600)
print("\(strHours):\(strMinutes):\(strSeconds)")
self.lblTimer.text = String(format: "%0.2d:%0.2d:%0.2d",strHours,strMinutes,strSeconds)
}
}
When you use the selector try self.update() to actually call the function. May also put a print statement to check that your variable is indeed incrementing.

Cannot select rows after tableView.reloadData()

In my application, I have a tableview that constantly (every 5 seconds) needs updating. To do this, I have set a timer which runs a method updating the data source (array) and calling the tableView.reloadData() method immediately afterwards. Unfortunately, a problem arises after the reloadData() method is called, one in which I cannot select a row without first scrolling! It's very strange. All I have to do is slightly make the table view scroll, and I can select cells again. Help!
viewDidLoad
DataHandler.updateCustomers() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.updateTable()
})
}
let _ = NSTimer.scheduledTimerWithTimeInterval(5, target: self, selector: "updateData", userInfo: nil, repeats: true)
updateData
func updateData(){
DataHandler.updateCustomers() {
if (self.activeIDs.indexPathForSelectedRow == nil){
self.activeIDs.reloadData()
}
}
print("Data updated")
}
Thanks in advance!

How to stop NSTimer?

I have a problem with invalidating different timers.
I have multiple timers (NSTimer) on a viewcontroller(settingsVC):
class settingsVC: UIViewController {
// I use 12 timers
var timer1 = NSTimer()
// Seconds to end the timer. Set 12 timers
let timeInterval1:NSTimeInterval = 10
var timer2 = NSTimer()
let timeInterval2:NSTimeInterval = 20
var timer3 = NSTimer()
let timeInterval3:NSTimeInterval = 30
//and so on ... 12 timers
}
With a UIButton (Start) a segue is performed. And for every different value of the variable 'picked', a different timer will be started in the same class:
class settingsVC: UIViewcontroller {
let defaults = NSUserDefaults.standardUserDefaults()
let pickerDefaultsIntegerKey = "Picker" // nsuserdefaults key
#IBAction func start(sender: AnyObject) {
// segue to another viewcontroller
performSegueWithIdentifier("timerOn", sender: self)
if picked == 1 {
defaults.setInteger(1, forKey: pickerDefaultsIntegerKey)
timer1 = NSTimer.scheduledTimerWithTimeInterval(timeInterval1,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer1 started")
} else if picked == 2 {
defaults.setInteger(2, forKey: pickerDefaultsIntegerKey)
timer2 = NSTimer.scheduledTimerWithTimeInterval(timeInterval2,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer2 started")
} else if // ....and so on{........ }
}
The method fired if timer ends, see selector:
func timerDidEnd(timer:NSTimer){
print("timer ended")
// do other stuff
}
I invalidate the timers with a button (Reset) for values from a variable ('pickerSavedSelection') which is updated by saved values in NSUserdefaults:
#IBAction func reset(sender: AnyObject) {
if let pickerSavedSelection = defaults.integerForKey(pickerDefaultsIntegerKey) as Int?
{
if pickerSavedSelection == 1 {
timer1.invalidate()
} else if pickerSavedSelection == 2 {
timer2.invalidate()
} else if //...and so on{....}
}
All goes well, if I outcomment the perform segue line and just let the user stay on this viewcontroller.The timers get invalidated correctly then:
In the console I read 'timer1 started' and I do NOT read 'timer ended' when the resetButton is pressed.
But staying on this viewcontroller(settingsVC) is NOT the flow of my app.
When the perform segue line is executed and the user 'comes back' to the viewcontroller (settingsVC), the timers are not invalidated when user presses the resetButton:
In the console I read 'timer1 started' and I DO read 'timer ended' when the resetButton is pressed.
How should I stop the timers, when users will 'exit' the viewcontroller and come back to reset the timers?
Help is much appreciated! Thanks in advance
If I am not mistaken at any given point in time, you are only triggering one NSTimer. All your different timers are differentiated only in time intervals. So, my suggestion would be to keep only one NSTimer and have your time interval differentiated. With different value picked you should first invalidate the timer and then restart it with new time interval. That said, your reset will then be much simplified and you do not need to save pickerSavedSelection in NSUserDefaults. This is how I would re-write this code:
class settingsVC: UIViewController {
var timer = NSTimer()
#IBAction func start(sender: AnyObject) {
// segue to another viewcontroller
performSegueWithIdentifier("timerOn", sender: self)
if picked == 1 {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(10,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer1 started")
} else if picked == 2 {
self.timer.invalidate()
self.timer = NSTimer.scheduledTimerWithTimeInterval(20,
target: self,
selector: "timerDidEnd:",
userInfo: nil,
repeats: false)
print("timer2 started")
} else if // ....and so on{........ }
}
#IBAction func reset(sender: AnyObject) {
self.timer.invalidate()
}
}
PS: As a side note, I would advise your NSTimer to start & stop from main thread. Use GCD for that.
It is because your selector is not called when your timer is invalidated, it is called everytime your timer is fired. Since the timer is non-repeat, the selector get called only once. When your press reset button, timer is actually invalidated, you just didn't know because you misunderstood scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: method.

Resources