How to check how long ago was the app last opened - ios

In my app, I want to check how long ago did the user last open the app and if it was 14 days or more, the user would receive a prompt saying they should be more active but I'm not too sure how to check how long ago was the app last used.

Store the current date in UserDefaults whenever the app becomes inactive (using the applicationWillResignActive app delegate).
Load the stored date (if any) from UserDefaults whenever the app becomes active (using the applicationDidBecomeActive app delegate). If there is a date (there won't be the first time the app is used), calculate the number of days between the retrieved date and the current date.
See Swift days between two NSDates for methods to calculate the difference between two dates. In short, you use the Calendar dateComponents(_, from:, to:) method.

In your AppDelegate:
func applicationWillResignActive(_ application: UIApplication) {
UserDefaults.standard.set(Date(), forKey: "LastOpened")
}
func applicationDidBecomeActive(_ application: UIApplication) {
guard let lastOpened = UserDefaults.standard.object(forKey: "LastOpened") as? Date else {
return
}
let elapsed = Calendar.current.dateComponents([.day], from: lastOpened, to: Date())
if elapsed >= 14 {
// show alert
}
}

save current Date(UserDefaults) on applicationWillTerminate(_:) & applicationDidEnterBackground(_:) methods.
check it on application(_:didFinishLaunchingWithOptions:)

Related

how to detect if a task has execute in the last 2 minutes in swift 3?

I have a project that has Json in it - when a user come back to the home page because of view didLoad method the app will start getting son again and I want this But I want the app detect that if the user has came back to the home page in the last 2 minutes the app doesn't get the json - simply I want to run a task when user go to a view controller but if user has came back to the view controller in the last 2 minutes the app doesn't execute task and for example if the user open the app and go to the another page after 3 minutes when he came back to the home page the task start - as you see here I can use timer but the timer will run the task every minutes I want to limit this as I said
weak var timer: Timer?
func startTimer() {
timer = Timer.scheduledTimer(withTimeInterval: 60.0, repeats: true) { [weak self] _ in
// do something here
}
}
func stopTimer() {
timer?.invalidate()
}
// if appropriate, make sure to stop your timer in `deinit`
deinit {
stopTimer()
}
You would need to save the Date(time) object when the task gets completed in the completion handler and then next time when you are about to start the task you would need to check the time elapsed.
Set a Date in UserDefaults in the completion handler of your task.
Before proceeding to start task check whether this Date exists and if exists, then the elapsed time is greater than 120 seconds(2 minutes) or not.
func startTaskIfPossible() {
let date = UserDefaults.standard.object(forKey: "taskCompletionDate") as? Date
guard let prevCompletionDate = date else {
startTask()
return
}
guard Date().timeIntervalSince(prevCompletionDate) > 120 else {
return
}
startTask()
}
func startTask() {
//Set Date in userdefaults in completion handler of task
// UserDefaults.standard.setValue(Date(), forKey: "taskCompletionDate")
}
Save the last json request execution time in one of the keys in your NSUserdefaults.
let userDefaults = NSUserDefaults.standardUserDefaults()
// save to user defaults
userDefaults.setObject(NSDate(), forKey: "LastExecutionDate")
Everytime your user come to home page and you want to fire json request, just compare it with last execution time. If its more than 2 mins fire it otherwise don't.
// retrieve from user defaults
let lastExecutionDate = userDefaults.objectForKey("LimitReachedOnDate") as? NSDate
Then your currentDate - lastExecutionDate > 180 seconds. This is just a algo and not the exact code for date comparison but i guess you will get it. this

How to use requestReview (SKStore​Review​Controller) to show review popup in the current viewController after a random period of time

I've read about this new feature available in iOS 10.3 and thought it will be more flexible and out of the box. But after I read the docs I found out that you need to decide the time to show it and the viewController who calls it. Is there any way I can make it trigger after a random period of time in any viewController is showing at that moment?
In your AppDelegate:
Swift:
import StoreKit
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let shortestTime: UInt32 = 50
let longestTime: UInt32 = 500
guard let timeInterval = TimeInterval(exactly: arc4random_uniform(longestTime - shortestTime) + shortestTime) else { return true }
Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(AppDelegate.requestReview), userInfo: nil, repeats: false)
}
#objc func requestReview() {
SKStoreReviewController.requestReview()
}
Objective-C:
#import <StoreKit/StoreKit.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
int shortestTime = 50;
int longestTime = 500;
int timeInterval = arc4random_uniform(longestTime - shortestTime) + shortestTime;
[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:#selector(requestReview) userInfo:nil repeats:NO];
}
- (void)requestReview {
[SKStoreReviewController requestReview];
}
The code above will ask Apple to prompt the user to rate the app at a random time between 50 and 500 seconds after the app finishes launching.
Remember that according to Apple's docs, there is no guarantee that the rating prompt will be presented when the requestReview is called.
For Objective - C:
Add StoreKit.framework
Then in your viewController.h
#import <StoreKit/StoreKit.h>
Then in your function call :
[SKStoreReviewController requestReview];
For Swift
Add StoreKit.framework
In your ViewController.swift
import StoreKit
Then in your function call :
if #available(iOS 10.3, *) {
SKStoreReviewController.requestReview()
} else {
// Open App Store with OpenURL method
}
That's it ! Apple will take care of when it would show the rating (randomly).
When in development it will get called every time you call it.
Edited : No need to check OS version, StoreKit won't popup if the OS is less than 10.3, thank Zakaria.
Popping up at a random time is not a good way to use that routine, and is not only in contravention of Apple's advice, but will give you less-than-great results.
Annoying the user with a pop up at a random time will never be as successful as prompting them at an appropriate time- such as when they have just completed a level or created a document, and have that warm fuzzy feeling of achievement.
Taking Peter Johnson's advice, I created a simple class where you can just stick the method in at the desired spot in your code and it'll pop up at a spot where the user's just had a success.
struct DefaultKeys {
static let uses = "uses"
}
class ReviewUtility {
// Default Keys stored in Structs.swift
static let sharedInstance = ReviewUtility()
private init() {}
func recordLaunch() {
let defaults = UserDefaults.standard
// if there's no value set when the app launches, create one
guard defaults.value(forKey: DefaultKeys.uses) != nil else { defaults.set(1, forKey: DefaultKeys.uses); return }
// read the value
var totalLaunches: Int = defaults.value(forKey: DefaultKeys.uses) as! Int
// increment it
totalLaunches += 1
// write the new value
UserDefaults.standard.set(totalLaunches, forKey: DefaultKeys.uses)
// pick whatever interval you want
if totalLaunches % 20 == 0 {
// not sure if necessary, but being neurotic
if #available(iOS 10.3, *) {
// do storekit review here
SKStoreReviewController.requestReview()
}
}
}
}
To use it, stick this where you want it to be called and hopefully you won't tick off users with randomness.
ReviewUtility.sharedInstance.recordLaunch()
Showing the dialog at random time is not probably a good idea. Please see the Apple guideline which mentions: Don’t interrupt the user, especially when they’re performing a time-sensitive or stressful task.
This is what Apple suggests:
Ask for a rating only after the user has demonstrated engagement with your app. For example, prompt the user upon the completion of a game level or productivity task. Never ask for a rating on first launch or during onboarding. Allow ample time to form an opinion.
Don’t be a pest. Repeated rating prompts can be irritating, and may even negatively influence the user’s opinion of your app. Allow at least a week or two between rating requests and only prompt again after the user has demonstrated additional engagement with your app.
This post is also quite interesting...
I cant add comments yet but if you are using Appirater you might want to check the version to see if its lower than 10.3 so the other Appirater review message box pops up.

What function gets called when loading app from homescreen?

I want to be able to change the background color of my app depending on the time of day. I believe the way to go about this is saying if the hour component is greater than whatever number, set the background to the nighttime background, otherwise it's the daytime background.
To test out what I was worried about, I threw
timeLabel.text = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .NoStyle, timeStyle: .FullStyle)
into viewDidLoad. This shows the time that the app loads. This also obviously keeps updating if I kill the app and reload it completely.
However, if the user goes the the home screen or goes to a different app, then comes back to this the time isn't going to be updated. It will show the time the app was first loaded up. Only if the user completely kills the app, which obviously can't be relied on, will the correct time be shown.
So in my example if my "switch to nighttime time" was 5pm, if the user loads up at the at at 4:30 and then goes to the homescreen, loads up the app at 5pm, nothing will be changed as the stored time will still be 4:30.
I tried throwing the code in viewDidAppear and nothing changed.
What code is run when the app is loaded up from being on the homescreen or coming back from another app?
You want to key off of the UIApplicationDidBecomeActiveNotification event. Try this:
override func viewDidLoad()
{
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(
self,
selector: "applicationDidBecomeActive:",
name: UIApplicationDidBecomeActiveNotification,
object: nil)
}
func applicationDidBecomeActive(notification: NSNotification)
{
// do something when the app is active again.
timeLabel.text = NSDateFormatter.localizedStringFromDate(NSDate(), dateStyle: .NoStyle, timeStyle: .FullStyle)
}
The method -applicationWillEnterForeground: in your application delegate will be called every time a user enters your app.
You'll want to look at using NSCalendar for this:
let currentDate = NSDate() // You can input the custom as well
let calendar = NSCalendar.currentCalendar()
let components = calendar.components(.CalendarUnitHour | .CalendarUnitMinute, fromDate: NSDate())
let currentHour = components.hour // You can play around with the ""components""
The following method is what you're after:
applicationDidBecomeActive: it's called every time a user opens the app.

Scheduled NSNotification (Instead of UILocalNotification)... Swift solutions?

My app (prefix "AAS") is basically a game where users lose points every day they don't play. I use UILocalNotifications to alert the user that they've lost points, and invite them back to play. One of my view controllers displays when the points have changed, and it's pretty simple to send out an NSNotification when a UILocalNotification is fired while the app is open).
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if notification.userInfo != nil {
if let notificationName = notification.userInfo![AASNotification.ActionKey] as? String {
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil, userInfo: nil)
}
}
}
When the app is reopened after being inactive, one of the classes calculates how many points are lost. Great. Bulletproof, except when the user disallows my app to use NotificationCenter, the app will not be updated if it's open when the notification is supposed to fire. For this case, I wrote my own implementation of a timed notification queue that would mimic UILocalNotification to a certain extent while my app is open. But I thought, someone must have had this problem before, and maybe there is a cocoapod for it.
So my question to the community is, does someone know of a library that dispatches timed NSNotifications? Or a different approach to this problem? Here's my solution, which is barebones and works for the purpose I need:
https://github.com/JamesPerlman/JPScheduledNotificationCenter
I'd love to use one that was coded by a professional and is well tested and feature rich. (I was made aware that this request is off topic for SO.)
Edits:
I want to be able to queue up any amount of NSNotifications to be fired at arbitrary dates. Obviously the NSNotifications can only be received by my app while it is open, that's fine. I do not know the expense of using one NSTimer for each NSNotification (could be hundreds of NSTimers all on the run loop), so my solution only uses one NSTimer at a time. I want the ability to schedule and cancel NSNotifications just like you can do with UINotifications.
You could try NSTimer (NSTimer class reference). In your AppDelegate you can create a method similar to your didReceiveLocalNotification method to execute when the timer is triggered. Also, create an NSUserDefault to store the next time you need to trigger the timer. Finally, at the point where you want to begin the countdown, get the time interval from the current time until the time you want to trigger the event, and set the timer.
So in your AppDelegate, register the default and implement the notifyPlayer:
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
let userDefaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.registerDefaults(["alertTime": NSDate()]) //initial value
return true
}
func notifyPlayer() {
// Calculate points and notify relevant viewcontroller to alert player.
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
let lastNotificationTime = defaults.objectForKey("alertTime") as! NSDate
let nextNotificationTime = lastNotificationTime.dateByAddingTimeInterval(86400)
defaults.setObject(nextNotificationTime, forKey: "alertTime")
}
}
Now set the timer wherever it makes sense, probably in your app's initial view controller.
class InitialVewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
let savedTime = defaults.objectForKey("alertTime") as! NSDate
let countDownTime = savedTime.timeIntervalSinceNow
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
NSTimer.scheduledTimerWithTimeInterval(countDownTime,
target: appDelegate,
selector: #selector(AppDelegate.notifyPlayer()),
userInfo: nil,
repeats: false)
}
}
It's not perfect, as I haven't tested it, but I think the concept will work for you.
Edit: Just to clarify, this would solve your problem of alerting the user while he is using the app, but won't do anything when the app is not in use. I don't know of any way to send users notification center notifications when permission hasn't been granted.

iOS disable UIButton for 30 minutes

I am making an app where people can request two orders per hour maximum. I would like to disable the "Order" UIButton for a full 30 minutes once it is pressed. Can anybody suggest the most efficient way to do this but that fully prevents the user from ordering twice even if the app is killed? Thanks.
At a high level you need to do two things:
Calculate the NSDate for when the button should be enabled again and store that date in NSUserDefaults.
Start an NSTimer that goes off in 30 minutes. Enable the disabled button when the timer goes off and remove the date from NSUserDefaults.
More than likely the app will go into the background and the timer will stop long before the 30 minutes. This means that your app needs to stop the timer when it goes into the background. And when it returns to the foreground, you look at the date in NSUserDefaults and see how much time is left. If the time is already past, enable the button and delete the date from NSUserDefaults. If not, start another timer to go off after the needed amount of time as in step 2 above.
Here's the approach I thought of earlier to your problem. The three things you'll use are NSDate, NSTimeInterval, and NSUserDefaults
// I threw this in Xcode to aide me in typing this solution.
// You probably dragged a button from storyboard...leave it like that.
let orderButton: UIButton?
// Put toggleOrderButtonAvailability() in viewDidLoad and viewDidAppear
func toggleOrderButtonAvailability() {
// get current date
let currentDate = NSDate()
// we're not sure if there's a value for this, but we're creating a variable for it
// it will nil if the user hasn't placed an order
var lastOrderDate: NSDate?
// we're creating a variable to check
var timeSinceLastOrder: NSTimeInterval?
// if a value for the lastOrderDate saved in NSUserDefaults, then...
if NSUserDefaults.standardUserDefaults().objectForKey("lastOrderDate") != nil {
lastOrderDate = NSUserDefaults.standardUserDefaults().valueForKey("lastOrderDate") as? NSDate
// calculate minutes since last order
// 1800 seconds = 60 seconds per minute X 30 minutes
timeSinceLastOrder = (currentDate.timeIntervalSinceDate(lastOrderDate!)) / 1800
if timeSinceLastOrder < 30 {
orderButton?.enabled = false
// Some alert to let the user know they can't order for another X minutes
// TODO: you could create a class variable like "timeUntilButtonReenabled"
// and set it here, then the timer would run and call this method when it's
// done to re-enable the button. Set the timer in viewDidAppear()
} else {
orderButton?.enabled = true
}
}
}
You'll also want to set the lastOrderDate when you place an order and you can call the method we just created to disable the button when you place an order.
#IBAction func orderButtonAction(sender: UIButton) {
// Whatever you do when you send an order
// Set the lastOrderDate & disable the button
let currentDate = NSDate()
NSUserDefaults.standardUserDefaults().setObject(currentDate, forKey: "lastOrderDate")
toggleOrderButtonAvailability()
}
You should save the order date in NSUserdefaults.Once app launched,check the last order date and make an count down timer for that.
When the button is pressed, disable the button and log the current time using an NSDate object. To ensure it persists even if the app is killed, make sure you write it-- if you're app isn't already using a data system, NSUserDefaults is probably the easiest way to get about this.
Next, you need to create a mechanism for the button to enable again. The easiest reliable method to do so is by creating an NSTimer that checks whether or not the logged date is over 30 minutes ago, and if so, enable the button.
Here's an example of how to do this in Swift:
class ViewController: UIViewController {
#IBOutlet weak var btn: UIButton!
var enableTimer: NSTimer!
let defaults = NSUserDefaults.standardUserDefaults()
//Because this is a computed property, it auto-saves and persists
var lastPushed: NSDate {
get {
if let unwrappedDate = defaults.objectForKey("lastPushed") as? NSDate {
return unwrappedDate
} else { //If date not yet set
return NSDate(timeIntervalSince1970: 0)
}
} set { //NSDate is already compatible with NSUserDefaults
defaults.setObject(newValue, forKey: "lastPushed")
}
}
override func viewDidLoad() {
startTimer()
}
func startTimer() {
enableTimer = NSTimer.scheduledTimerWithTimeInterval(30, self, Selector("enableTim:"), nil, true)
}
#IBAction btnPressed() {
lastPushed = NSDate() //NSDate with current time
startTimer()
btn.enabled = false
}
func enableTim(timer: NSTimer) {
if (lastPushed.timeIntervalSinceNow < -1800) { //If last pressed more than 30 minutes ago
btn.enabled = true
enableTimer.stop()
}
}
}

Resources