Using NSUser Defaults to save date and run background timers

I'm trying to save the date that the user initially launches my app, but I'm not sure how to use timers to do the following:

  • Check if date has already been saved. (done)
  • If not, save it (means initial launch of app) (done)
  • If so, check to see how much time has passed since that date. If its been one week, show hidden button, and send user notification that button is no longer hidden (need help with the timer)
    I'm using Swift 4, but I have little experience in Swift in general
    var installedDate: Date? {
        get {
            return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "installedDateKey")
        }
    }
override func viewDidLoad() {
    super.viewDidLoad()
    
    if installedDate == nil {
        installedDate = Date()
        print(installedDate)
        print("First run")
    }
    else {
        print("Not first run")
        print(installedDate!)
    }
}

This is not necessarily easy, depending on your exact requirements. You wrote:

check to see how much time has passed since that date. If its been one
week

How do you define a week? Do you care about:

  • Time vs days? If I say “Do this in a week?”, I probably don’t mean “at 10:06:30 on Tue”, I mean “on Tue”.

  • Local time? If you care about time, do you want use local time for the deadline. If I say, “do this at 10:06:30 on Tue” and then the device moves from GMT+1 to GMT-7, do you want it done at 09:06:30 GMT or 02:06:30 GMT? Even users who don’t move can run into this issue with daylight saving time.

  • Clock changes? Do you care if the user changes their clock?

The simplest way to do this is to avoid the concept of an ‘installed date’ and instead work with a deadline. Calendar makes this relatively straightforward:

let now = Date()
let cal = Calendar(identifier: .gregorian)
let deadline = cal.date(byAdding: .weekOfYear, value: 1, to: now)!
print(now)          // 2019-04-02 09:06:30 +0000
print(deadline)     // 2019-04-09 09:06:30 +0000

Note that I’ve hard-wired the Gregorian calendar here, because you don’t want this calculating being affected by the user’s chosen calendar (last I checked all of our calendars treat a week as seven days, but there’s no point relying on that if you don’t have to).

Once you have a deadline date, you can set a timer for that date:

let timer = Timer(fire: deadline, interval: 0.0, repeats: false) { _ in
    … your code here …
}
RunLoop.current.add(timer, forMode: .default)

WARNING If you’re working on an iOS-based platform, it’s critical that you invalidate this timer when you becomes eligible for suspension (in a simple app this is when you go into the background) and then re-install it when you resume (assuming a simple app, this is when you come back to the foreground). Timers can do weird things if you leave them running while suspended.

When you come to the foreground you can also take the opportunity to see if the deadline has expired while you were suspended.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple