Switch from Countdown to Countup - swift

stackoverflow is my last chance. I hope you can help me. I have a countdown in my app which is set to 2 hours and 30 minutes. When it hits 0 it starts to count up to 30 minutes. After the 30 minutes are over, the countdown is back at 2:30. Everything is working fine except the part when the timer is switching from the countdown part to the countup part. Here is the problem: The timer is going negative when hitting zero.
#objc func startTimer() {
print("Countdown : \(totalTime)")
countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#objc func updateTime() {
if (totalTime > 10800) {
totalTime = totalTime - 10800
}
totalTime -= 1
var time = totalTime
//When in session...
if (time > 9000) {
//Calculate CountUP
time = 10800 - time
//if session has started
if(isSessionActive == false) {
isSessionActive = true
lastFiveMinutes = false
self.setSessionStarted()
}
} else {
//If session has ended...
if(isSessionActive == true) {
isSessionActive = false
lastFiveMinutes = false
self.setSessionEnded()
}
//If last 5 minutes have started
if(time < 300 && lastFiveMinutes == false) {
lastFiveMinutes = true
self.setLastFiveMinutes()
}
}
countdownLabel.text = "\(timeFormatted(time))"
//update Participant Count every x second, ONLY if in last 5 Minutes or Session is active
if (lastFiveMinutes == true || isSessionActive == true) {
if (totalTime % 5 == 0) {
setParticipants(n: httpController.getPar())
}
} else {
setParticipants(n: "...")
}
}
The part "var time = totalTime" is not necessary, i know.
In Android the code is exactly the same except the -=1 part, but it is working.
* Update *
Inital Value of totalTime:
#objc var totalTime = 0
And here is the next part where it gets a real value.
#objc func initCountdown() {
var currentSec = 0.0
// Get Current Time from Server
serverTimeReturn { (getResDate) -> Void in
let dFormatter = DateFormatter()
dFormatter.dateStyle = DateFormatter.Style.long
dFormatter.timeStyle = DateFormatter.Style.long
dFormatter.timeZone = NSTimeZone(abbreviation: "GMT")! as TimeZone
let dateGet = dFormatter.string(from: getResDate! as Date)
currentSec = Double((getResDate?.timeIntervalSince1970)!)
print("Formatted Hour : \(dateGet)")
let todaySec = Int(currentSec) % 86400
print("Today Sec Hour : \(todaySec)")
let countDownSec = 10800 - todaySec % 10800
// Check status to init
if(countDownSec > 9000) {
self.isSessionActive = true
self.setSessionStarted()
} else {
self.setSessionEnded()
}
if(countDownSec < 300) {
self.setLastFiveMinutes()
self.lastFiveMinutes = true
}
self.totalTime = countDownSec
}
print("Formatted Hour : \(totalTime)")
startTimer()
}

I don't see any code where you count upwards so I am not surprised it isn't working. I would introduce a boolean property isCountingDown to keep track of how to handle the timer and then have two separate methods for up and down
var isCountingDown: boolean
#objc func initCountdown() {
//existing code
isCountingDown = true
startTimer()
}
#objc func updateTime() {
if isCountingDown {
doCountDown()
} else {
doCountUp()
}
}
And then in doCountDown() and doCountUp() I would check against time is 0 or 30 and update isCountingDown accordingly
func doCountDown {
if (totalTime > 10800) {
totalTime = totalTime - 10800
}
totalTime -= 1
if totalTime == 0 {
isCountingDown = false
//Maybe return here?
}
var time = totalTime
//rest of code
}
func doCountUp() {
totalTime += 1
if totalTime == 30 {
isCountingDown = true
//Maybe return here?
}
//rest of code
}

Related

How to make the value of a variable reset every day

I want to make an exercise app to help users track their daily exercise and I have a variable exerciseTime that I want to reset every day. Is there any way to do that?
The variable is stored in app storage #AppStorage("exerciseTime") var exerciseTime = 0.0 and connected to a timer.
Text("Time: \(format(seconds: timerStruct.countdownTimer))")
.padding()
.onReceive(timer) { _ in
if timerStruct.countdownTimer > 0 && timerStruct.timerRunning == true {
timerStruct.countdownTimer -= 1
exerciseTime += 1.0
} else {
timerStruct.timerRunning = false
if timerStruct.countdownTimer <= 0, timerStruct.timerRunning == false {
timerStruct.isAlertpresented = true
reset()
}
}
This function checks if the current date is in the same day as a saved date in UserDefaults
Just call it always when the app becomes active
func isNewDay() -> Bool {
let defaults = UserDefaults.standard
let now = Date.now
if let savedDate = defaults.object(forKey: "currentDate") as? Date,
Calendar.current.compare(savedDate, to: now, toGranularity: .day) == .orderedSame {
return false
}
defaults.set(now, forKey: "currentDate")
return true
}

SwiftUI: How to run a CountdownTimer in background

How make CountdownTimer and how make let elapsed = public var . I want to make the variable constantly decrease life.
For example, as in Tamagotchi - You need to constantly feed the animal so that it does not die.
This timer should continuously run until it reaches 0. When the tamagotchi eats, the value is again added to the variable, which decreases over time.
class Stopwatch: ObservableObject {
/// String to show in UI
#Published private(set) var message = "Not running"
/// Is the timer running?
#Published private(set) var isRunning = false
/// Time that we're counting from
private var startTime: Date? { didSet { saveStartTime() } }
/// The timer
private var timer: AnyCancellable?
init() {
startTime = fetchStartTime()
if startTime != nil {
start()
}
}
}
extension Stopwatch {
func start() {
timer?.cancel() // cancel timer if any
if startTime == nil {
startTime = Date()
}
message = ""
timer = Timer
.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard
let self = self,
let startTime = self.startTime
else { return }
let now = Date()
let elapsed = now.timeIntervalSince(startTime)
guard elapsed < 60 else {
self.stop()
return
}
self.message = String(format: "%0.1f", elapsed)
}
isRunning = true
}
func stop() {
timer?.cancel()
timer = nil
startTime = nil
isRunning = false
message = "Not running"
}
}
private extension Stopwatch {
func saveStartTime() {
if let startTime = startTime {
UserDefaults.standard.set(startTime, forKey: "startTime")
} else {
UserDefaults.standard.removeObject(forKey: "startTime")
}
}
func fetchStartTime() -> Date? {
UserDefaults.standard.object(forKey: "startTime") as? Date
}
}

trigger 2 different actions depending on duration between 2 ‘onTap’

For a button, each time it is tapped, how to trigger a code1 (set a specific var to value1) when ‘onTap’ occured less than 60 seconds ago or code2 (set a specific variable to value2) when onTap occured more than 60 seconds ago ?
==> Working solution implemented for duration = 10 seconds
Button(action: {
self.date = Date()
let hour = self.calendar.component(.hour, from: self.date)
let minutes = self.calendar.component(.minute, from: self.date)
let seconds = self.calendar.component(.second, from: self.date)
self.newdate = hour*3600+minutes*60+seconds
if (self.lastdate == 0){
print(" First Tap...")
}else
if (self.newdate-self.lastdate) < 10 {
print("Tap before 10 sec => call Func1")
} else {
print("Tap > 10 seconds => call Func2")
}
self.lastdate = hour*3600+minutes*60+seconds
}) {
Text("One")
}
Here's a complete example that works for an interval of 5 seconds (to make testing quicker).
It uses #State vars to update the Date of the lastTap and message and uses .timeIntervalSince(last) to measure the time in seconds. The first tap is detected when lastTap is still nil.
Changing the message causes the view to redraw and update the message on the screen.
struct ContentView: View {
#State private var lastTap: Date? = nil
#State private var message = ""
var body: some View {
VStack(spacing: 40) {
Text(message)
Button("Tap me") {
let now = Date()
if let last = self.lastTap {
if now.timeIntervalSince(last) > 5 {
self.message = "Greater than 5 seconds"
} else {
self.message = "Less than 5 seconds"
}
} else {
self.message = "First tap"
}
self.lastTap = now
}
}
}
}
var lastTapTimestamp: Date?
#IBAction func buttonTapped(_ sender: UIButton) {
defer { lastTapTimestamp = Date() }
guard let date = lastTapTimestamp else {
callFunc1()
return
}
if Int(Date().timeIntervalSinceReferenceDate) - Int(date.timeIntervalSinceReferenceDate) >= 3600 {
callFunc2()
} else {
callFunc1()
}
}

Which "Leap year counting" code run faster?

while practicing coding, I just caught up with question: which code will process faster in swift?
Is this one faster:
var aYear = Int(readLine()!)!
func isLeap(year: Int)
{
if aYear%400 == 0
{
print("YES")
}
else if aYear%4 == 0 && !(aYear%100 == 0)
{
print("YES")
}
else
{
print("NO")
}
}
isLeap(year: aYear)
Or this one faster?
var aYear = Int(readLine()!)!
func isLeap(year: Int) {
var leap = "NO"
if year % 4 == 0 {
leap = "YES"
}
if year % 100 == 0 {
leap = "NO"
}
if year % 400 == 0 {
leap = "YES"
}
print(leap)
}
isLeap(year: aYear)
Thank you. Have a great day :)
One way to check function performance is to this helper struct. Add the following to your project:
struct Benchmark {
static func testElapsedTimeFor(_ title: String, operation: #escaping ()->()) {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(title): \(timeElapsed) s.")
}
// If you would like to get the double instead
static func testElapsedTimeFor(operation: #escaping ()->()) -> Double {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
return Double(timeElapsed)
}
}
And use it like so:
Benchmark.testElapsedTimeFor("First Function") {
self.isLeap(year: 2019)
}

AVPlayer currentTime wrong when rewind

I have AVPlayer when I rewind an audio file, then the current time is longer than the total file duration. Who knows what the problem is, why is the current time wrong?
#objc func updateProgressBar(){
guard let value = AppDelegate.avPlayer.currentItem?.currentTime().seconds else { return }
let time = Func.getHoursMinutesSecondsFrom(seconds: value)
DispatchQueue.main.async {
self.startTime.text = time.fullTime
}
}
func durationAudio(){
// расчитывает время аудиозвука
guard let duration = AppDelegate.avPlayer.currentItem?.asset.duration else { return }
let time = Func.getHoursMinutesSecondsFrom(seconds: CMTimeGetSeconds(duration))
DispatchQueue.main.async {
self.endTime.text = time.fullTime
}
}
converts to hours, minutes, seconds
static func getHoursMinutesSecondsFrom(seconds: Double) -> (hours: Int, minutes: Int, seconds: Int, fullTime:String) {
let secs = Int(seconds)
let hours = secs / 3600
let minutes = (secs % 3600) / 60
let seconds = (secs % 3600) % 60
let duration:String!
if hours != 0 {
duration = String(format:"%02d:%02d:%02d", hours, minutes, seconds)
} else {
duration = String(format:"%02d:%02d", minutes, seconds)
}
return (hours, minutes, seconds, duration)
}
rewind audio
func seekTo(completion:Bool){
let duration = CMTimeGetSeconds(AppDelegate.avPlayer.currentItem!.asset.duration)
let value = self.sliderSong.value
let durationToSeek = Float(duration) * value
let timeScale = AppDelegate.avPlayer.currentItem!.duration.timescale
AppDelegate.avPlayer.seek(to: CMTimeMakeWithSeconds(Float64(durationToSeek), preferredTimescale: timeScale), toleranceBefore: CMTime.zero, toleranceAfter: CMTime.zero) { [weak self](bool) in
guard completion else { return }
self?.seeking = false
}
}