0

I want to save a timer using NSUserDefaults to accomplish my goal. Unfortunately when I try to save the NSDate() using NSUserDefaults, it becomes static and won't continue counting. Am I doing something wrong? Again my goal is have the timer still work regardless of whether it went to background or got terminated. It should save the current time and compare it to how much time is remaining. Here is my code so far. Other code addressing this issue is in OBJ C, and used didEnterBackground which is not necessary.

func startTimer() {
    // then set time interval to expirationDate…

    expirationDate = NSDate(timeIntervalSinceNow: 86400 )
    dateTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(QOTDVC.updateUI(timer:)), userInfo: nil, repeats: true)
    RunLoop.current.add(dateTimer, forMode: RunLoopMode.commonModes)
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "TimerAdded"), object: nil)

}

func updateUI(timer: Timer)
{
    // Call the currentTimeString method which can decrease the time..
    let timeString = currentTimeString()
    timerLabel.text = "\(timeString)"
}
func currentTimeString() -> DateComponents  {
    let unitFlags: Set<Calendar.Component> = [.hour, .minute, .second]



    let countdown: DateComponents = Calendar.current.dateComponents(unitFlags, from: defaults.object(forKey: currentTime.description) as! Date, to: expirationDate as Date)


    print("this is the \(countdown)")

    if countdown.second! > 0 {

    } else {
        dateTimer.invalidate()


    }
        return countdown
}
bradford gray
  • 537
  • 2
  • 5
  • 18
  • 2
    You can't save a timer to `NSUserDefaults`. See this question: http://stackoverflow.com/questions/22628922/how-to-run-nstimer-in-background-beyond-180sec-in-ios-7 – random Sep 28 '16 at 16:56
  • I read: http://stackoverflow.com/questions/39753403/how-to-save-nstimer-using-nsuserdefaults-swift?noredirect=1#comment66803063_39753403 , and that seems to say otherwise. I'm just not understanding why it's staying static. – bradford gray Sep 28 '16 at 17:11
  • 1
    Since your timer counts seconds why don't you use the system time? Save the current time and the elapsed seconds from your counter when the app goes to the background. – vadian Sep 28 '16 at 17:16
  • What are you actually trying to achieve? Are you imagining that a timer could continue counting down while you store it in NSUserDefaults? – gnasher729 Sep 28 '16 at 19:01
  • No I realize that it cannot do that. My goal is save the end time with NSUserDefaults and compare it when the app gets loaded and the current Date(). This way I see how much time is remaining and the timer continues from that. – bradford gray Sep 28 '16 at 19:22

1 Answers1

4

Here is my full code example. I hope this is what you wanted.

import UIKit

protocol UserDefaultsTimerDelegate {
    func timerAction(timer: Timer, secondsToEnd:Int)
}

class UserDefaultsTimer {

static var delegate: UserDefaultsTimerDelegate?

class var timerEndDate: Date? {
    get {
        return UserDefaults.standard.value(forKey: "timerEndDate") as! Date?
    }
    set (newValue) {
        UserDefaults.standard.setValue(newValue, forKey: "timerEndDate")
    }
}

class var timerInited: Bool {
    get {
        if let _ = timerEndDate {
            return true
        } else {
            return false
        }
    }
}

class func setTimer(date: Date, setDateOnlyIfCurrenTimerIsOver: Bool) {
    if !setDateOnlyIfCurrenTimerIsOver {
        timerEndDate = date
    } else {
        if !timerInited {
            timerEndDate = date
        } else {
            let difference = timerEndDate!.seconds(from: Date())
            if (difference <= 0) {
                timerEndDate = date
            }
        }
    }
}

class func resetTimer() {
     timerEndDate = nil
}

class func resumeTimer() {
    if timerInited {
        NSLog("timer end date:\(timerEndDate)")
        let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(action(timer:)), userInfo: nil, repeats: true)
        RunLoop.current.add(timer, forMode: RunLoopMode.commonModes)
    }
}

@objc class func action(timer: Timer) {
    if let timerEndDate = timerEndDate {

        let difference = timerEndDate.seconds(from: timer.fireDate)
        if let delegate = delegate {
            delegate.timerAction(timer: timer, secondsToEnd: difference)
        }

        NSLog("timer: \(difference)")
        if (difference <= 0) {
            timer.invalidate()
            resetTimer()
        }
    } else {
        timer.invalidate()
        resetTimer()
    }
}
}

extension Date {
/// Returns the amount of years from another date
func years(from date: Date) -> Int {
    return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
}
/// Returns the amount of months from another date
func months(from date: Date) -> Int {
    return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
}
/// Returns the amount of weeks from another date
func weeks(from date: Date) -> Int {
    return Calendar.current.dateComponents([.weekOfYear], from: date, to: self).weekOfYear ?? 0
}
/// Returns the amount of days from another date
func days(from date: Date) -> Int {
    return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
/// Returns the amount of hours from another date
func hours(from date: Date) -> Int {
    return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
}
/// Returns the amount of minutes from another date
func minutes(from date: Date) -> Int {
    return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
/// Returns the amount of seconds from another date
func seconds(from date: Date) -> Int {
    return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
}
/// Returns the a custom time interval description from another date
func offset(from date: Date) -> String {
    if years(from: date)   > 0 { return "\(years(from: date))y"   }
    if months(from: date)  > 0 { return "\(months(from: date))M"  }
    if weeks(from: date)   > 0 { return "\(weeks(from: date))w"   }
    if days(from: date)    > 0 { return "\(days(from: date))d"    }
    if hours(from: date)   > 0 { return "\(hours(from: date))h"   }
    if minutes(from: date) > 0 { return "\(minutes(from: date))m" }
    if seconds(from: date) > 0 { return "\(seconds(from: date))s" }
    return ""
}
}

class ViewController: UIViewController, UserDefaultsTimerDelegate {

var label = UILabel(frame: CGRect(x: 40, y: 40, width: 60, height: 20))

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    label.text = ""
    label.textColor = UIColor.black
    view.addSubview(label)
    UserDefaultsTimer.delegate = self
    UserDefaultsTimer.setTimer(date: Date(timeIntervalSinceNow: 50), setDateOnlyIfCurrenTimerIsOver: true)
    UserDefaultsTimer.resumeTimer()
}

func timerAction(timer: Timer, secondsToEnd: Int) {
    label.text = "\(secondsToEnd)"
}
}
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • So, did you test this solution? – Vasily Bodnarchuk Sep 30 '16 at 08:13
  • Vasily ... How do I use this code? I am trying to make a stopwatch timer. I don't know which part of the code I would use for Start/Stop Button and reset etc. The code isn't anything like I've seen for accessing the func's. Thanks for your time helping. – David_2877 Aug 02 '20 at 15:18
  • @David_2877. hi! This is full sample. Just create a new project and run this code. You can see, how it works and update it. – Vasily Bodnarchuk Aug 02 '20 at 15:20
  • Thank you for your feedback. Yes the countdown timer works well. Closing down the app and reopening it using Xcode run and stop app. I just need to figure out how to make the timer count upwards and implement this into my project onto a specific UILabel. The only changes I've had to get Xcode to make are these lines in the func resumeTimer() NSLog("timer end date:\(String(describing: timerEndDate))") RunLoop.current.add(timer, forMode: RunLoop.Mode.common) – David_2877 Aug 04 '20 at 12:42
  • I've found how to change the countdown to CountUP. just removing once feature timeIntervalSinceNow: 50 to UserDefaultsTimer.setTimer(date: Date() , setDateOnlyIfCurrenTimerIsOver: true) then... to change the -ve number that comes up, add abs() to the function action area to show let difference = abs(timerEndDate.seconds(from: timer.fireDate)) – David_2877 Aug 04 '20 at 15:32
  • Yes thank you. I was able to implement and adapt it into my project and have two timers running for different purposes. Thank you so much for posting this answer. It's saved a lot of head scratching and importantly...time. – David_2877 Aug 06 '20 at 05:38