I'm using Google Firestore as my backend server for an iOS app I'm making. I am trying to incorporate a countdown timer to represent when the game starts, however, it is vital to use network time in oppose to device time to ensure the game starts for everyone at the same time.
The basic idea is to retrieve the current timestamp from Firebase, subtract it from another timestamp value of when I want the game to start and then keep refreshing the button text every second (not label because I need to be able to press it to reveal further information) with the time difference.
When the app starts, the timer works really well with no/very minimal lag however, if I change the value of when I want the game to start, the button text glitches for some reason. Even in the debug area, I can see that it's lagging.
I've done all the math and conversions. The current timestamp is a timeIntervalSince1970 value which I converted to a string with format "HH:mm:ss". I am retrieving this value from firebase server value. The other timestamp in which when I want the game to start is a string value I have stored in a collection in Firestore.
I send these two string values to a function that finds the difference for me and I set the button text to the value. It's simple but I cannot understand why it starts to lag when I change the second timestamp value. If I change it a third time, the app still runs but the timer basically freezes every 8 seconds or so.
override func viewDidAppear(_ animated: Bool) {
DispatchQueue.main.async {
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
Firestore.firestore().collection("countdown").document("thursday")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let theTime = document.data() else {
print("Document data was empty.")
return
}
print("Current data: \(theTime)")
let stringData = "\(theTime)"
let finalTime = stringData.substring(from: 9, to: 16)
// Maybe not the most conventional method to get the right format but it works for me. I'm open to suggestions of course tho.
var currentTimeStamp: Double?
let ref = Database.database().reference().child("serverTimestamp")
ref.setValue(ServerValue.timestamp())
ref.observe(.value, with: { snap in
if let t = snap.value as? Double {
currentTimeStamp = t/1000
let unixTimestamp = currentTimeStamp
let date = Date(timeIntervalSince1970: unixTimestamp!)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "EDT") //Set timezone that you want
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = "HH:mm:ss" //Specify your format that you
let strDate = dateFormatter.string(from: date)
let dateDiff = self.findDateDiff(time1Str: strDate, time2Str: finalTime)
print("This is the countdown time: \(dateDiff)")
}
})
}
}
}
// This is just a peak inside the function that does all the math and outputs the countdown time in the button text. I have many if statements for all the scenarios for the time to math the "HH:mm:ss" format.
if hours >= 10 && minutes >= 10 && seconds < 10{
self.time.setTitle(("\(Int(hours)):\(Int(minutes)):0\(Int(seconds))"), for: .normal)
I expect the button text to refresh with the correct countdown time. I expect that when I change my Firestore timestamp value, the button text refreshes automatically and does not lag.
The actual result is that it lags.
So I'm not sure if I have this correct, but my understanding is that you're setting a Timer
object to fire off an .observe
call each second? If so your Firebase calls aren't retrieving the data in time (.observe
is async, so that's where the lag comes from).
What I would suggest doing instead would be to first set the timestamp you're counting down to and post that value to Firebase.
When each device needs the countdown button, they retrieve the timestamp, then set the Timer
object to fire in 1 second intervals to retrieve the current time from something like NSDate().timeIntervalFrom1970, find the difference between the two, then set the button text (and deactivate the Timer
once the countdown is over). Maybe something like this?:
override func viewDidAppear(_ animated: Bool) {
// get time to count down from
Firestore.firestore().collection("countdown").document("thursday").getDocument { (document, error) in
guard let document = document, document.exists else {
// document doesn't exist
return
}
guard let theTime = document.data() else {
print("Document data was empty.")
return
}
print("Current data: \(theTime)")
let stringData = "\(theTime)"
let finalTime = stringData.substring(from: 9, to: 16)
self.setTimer(for: finalTime)
}
}
// set Timer
func setTimer(for finalTime: String) {
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
let timeNow = Date()
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "EDT")
dateFormatter.locale = NSLocale.current
dateFormatter.dateFormat = "HH:mm:ss"
let strDate = dateFormatter.string(from: timeNow)
// stop timer once finalTime reached. Idk what finalDate is but here's if it's formatted the same as strDate is.
guard finalTime != strDate else {
timer.invalidate()
print("countdown over")
return
}
let dateDiff = self.findDateDiff(time1Str: strDate, time2Str: finalTime)
print("This is the countdown time: \(dateDiff)")
}
timer.fire()
}
Also, I would suggest comparing timestamp values as Doubles and then formatting the difference how you need to instead of formatting each variable and then comparing them as strings