Working on a quote app, and as a beginner I decided to rule out using CoreData and Sqlite in my app. Therefore I decided to try a collection and change the text label.I have a collection stored in an array. I'm trying to achieve the text changing every 24 hours and it changes at 8:00 A.M E.S.T (so 8 A.M to 8 A.M)I would like the outline to be something like
quoteindex = 0
if(time_elasped:24 hours something about 8:00 A.M EST) {
quote.text = quoteCollection.quoteArray[quoteIndex]
quoteindex++ (next quote in array)
}
How would I organize something like this in terms of syntax? Would I use another loop?
An easy way to do this would be to use NSUserDefaults to store an NSDictionary containing the last time and index when the user last retrieved a quote.
in viewDidLoad: (or make into standalone function - checkLastRetrieval())
let userDefaults = NSUserDefaults.standardUserDefaults()
if let lastRetrieval = userDefaults.dictionaryForKey("lastRetrieval") {
if let lastDate = lastRetrieval["date"] as? NSDate {
if let index = lastRetrieval["index"] as? Int {
if abs(lastDate.timeIntervalSinceNow) > 86400 { // seconds in 24 hours
// Time to change the label
var nextIndex = index + 1
// Check to see if next incremented index is out of bounds
if self.myQuoteArray.count <= nextIndex {
// Move index back to zero? Behavior up to you...
nextIndex = 0
}
self.myLabel.text = self.myQuoteArray[nextIndex]
let lastRetrieval : [NSObject : AnyObject] = [
"date" : NSDate(),
"index" : nextIndex
]
userDefaults.setObject(lastRetrieval, forKey: "lastRetrieval")
userDefaults.synchronize()
}
// Do nothing, not enough time has elapsed to change labels
}
}
} else {
// No dictionary found, show first quote
self.myLabel.text = self.myQuoteArray.first!
// Make new dictionary and save to NSUserDefaults
let lastRetrieval : [NSObject : AnyObject] = [
"date" : NSDate(),
"index" : 0
]
userDefaults.setObject(lastRetrieval, forKey: "lastRetrieval")
userDefaults.synchronize()
}
You can be more specific using NSDate if you want to ensure a specific time (like 8AM) or make sure that there is a unique quote for every actual day (Monday, Tuesday, etc). This example simply changes the label if the user has seen a quote more than 24 hours ago.
Check out the docs for NSUserDefaults.
EDIT:
If you wanted to notify the user at 8AM the next day of a new quote you could send a local notification to the user.
let notification = UILocalNotification()
notification.fireDate = NSDate(timeIntervalSinceNow: someTimeInterval)
notification.timeZone = NSCalender.currentCalendar().timeZone
notification.alertBody = "Some quote" // or "Check the app"
notiication.hasAction = true
notification.alertAction = "View"
application.scheduleLocalNotification(notification)
You would have to calculate the timeInterval to be whatever time is left to 8AM the next day. Check this answer out: https://stackoverflow.com/a/15262058/2881524 (It's objective-c but you should be able to figure it out)
EDIT
To execute code when your view enters the foreground you need to post a notification in your AppDelegate's applicationWillEnterForeground method. And add an observer for that notification in your view controller.
in AppDelegate
let notification = NSNotification(name: "CheckLastQuoteRetrieval", object: nil)
NSNotificationCenter.defaultCenter().postNotification(notification)
in ViewController
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("checkLastRetrieval"), name: "CheckLastQuoteRetrieval", object: nil)
checkLastRetrieval()
}
Related
I have some code I want to run after a particular date/time has passed. For example, if I want the code to run 7 days from now and the user opens the app at any time on day 7 or after the code will run but if they open the app before the beginning of day 7 nothing happens. Timers in the main runloop work but only if the app is still running in the background. I need a method that will work even if the user kills the app.
Your best option is to store it as local data Even though you only want the code to run once, the overhead is so low, the "check" will not impact the speed or feel of the application. Also this will allow you to run additional checks .. If someone deletes the app, for instance, and leaves the local storage behind. If they re-install you could theoretically "remember" that the application has been installed, and said code has already run (until the user clears application data)
Something like:
//Globally set key
struct defaultsKeys {
static let keyDate = "dateKey"
}
// Set the date in local storage
let defaults = UserDefaults.standard
defaults.set("Your Date String", forKey: defaultsKeys.dateKey)
// Get the date from local storage
let defaults = UserDefaults.standard
if let stringDate = defaults.string(forKey: defaultsKeys.dateKey) {
print(stringDate)
// Do your date comparison here
}
Very few lines of code, and even though the check happens every time the application starts .. The overhead is negligible.
You can either set the date you want your app to "remember" on your local storage or web service. Then, when the user opens your app, compare that date to current device time to determine if you should execute your code.
First, save the current time when you want. You can set the key name however you want.
UserDefaults.standard.setValue(Date(), forKey: "rememberTime")
And every time I open the app, You compare the current time with the saved time.
To do so, I created a function that compares time.
extension Date {
func timeAgoSince() -> Bool {
let calendar = Calendar.current
let unitFlags: NSCalendar.Unit = [.day]
let components = (calendar as NSCalendar).components(unitFlags, from: self, to: Date(), options: [])
if let day = components.day, day >= 7 {
// Returns true if more than 7 days have passed.
return true
}
return false
}
}
Recall the previously saved time and use the time comparison function.
let beforeTime: Date = (UserDefaults.standard.object(forKey: "rememberTime") as? Date)!
if beforeTime.timeAgoSince() {
// more than seven days later
...
} else {
...
}
If you have a problem, please leave a comment !
You can use the below sample code:
override func viewDidLoad() {
super.viewDidLoad()
let nextCodeRunDate = Date() + (7 * 24 * 60 * 60) // 7 Days
if let savedDate = UserDefaults.standard.value(forKey: "NEXT_DATE") as? Date {
if Date() > savedDate {
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}else {
// First time
UserDefaults.standard.setValue(nextCodeRunDate, forKey: "NEXT_DATE")
runYourCode()
}
}
func runYourCode() {
// Your code
}
I'm completely lost in finding a tutorial or some sort of answer.
I'm trying to add a number (+1 for example), to a variable (that gets saved locally), every said amount of time (24 hours). Even if the user doesn't open/run the app.
Example: A mobile game named, "Cookie Clicker", kind of has this same functionality. It gives the user cookies depending on how much time was spent not playing the game/being offline.
My Question: How can I add +1 to a variable every 24 hours, regardless of if the user opens the app.
Code I Currently Have:
let daysOffDesfult = UserDefaults.standard
var daysOff = 0
//After 24 Hours: Call updateDaysOff() Function (This is the code I need.)
//Code Used to Save Variable Locally:
//Display the Updated Variable
if (daysOffDesfult.value(forKey: "daysOff") != nil){
daysOff = daysOffDesfult.value(forKey: "daysOff") as! NSInteger!
countLabel.text = "\(daysOff)"
}
//Update the Variable
func updateDaysOff() {
daysOff = daysOff + 1
countLabel.text = "\(daysOff)"
let daysOffDesfult = UserDefaults.standard
daysOffDesfult.setValue(daysOff, forKey: "daysOff")
daysOffDesfult.synchronize()
}
You can save the time of first open app in viewDidLoad or in your first UIViewController in UserDefaults like this:
if UserDefaults.standard.value(forKey: "firstDate") == nil {
UserDefaults.standard.set(Date(), forKey: "firstDate")
}
and every time app launches you compare the current date to date that you saved in UserDefaults to find out how many days passed
let savedDate = UserDefaults.standard.value(forKey: "firstDate")
let currentDate = Date()
let diffInDays = Calendar.current.dateComponents([.day], from: savedDate, to: currentDate).day
I would like to count how many continuous days user has used the app.
It updates the label depending on the streaks and if user has not used the app for a day the number goes back to zero.
How can I achieve this? I have searched but could not find any source.
For this you have a few things to take into consideration:
When to report last usage?
Your app idea may include the need to perform some actions before
considering a complete usage. For example, after loading or presenting something on the screen, after retrieving data and performing some actions, etc.
Just by intention of opening the app. The only intention is for the user to hit your app´s icon to launch the app, nevermind if he set it to a close state before even passing your loading screen.
This can be a bit unpredictable
When sending the app to background.
Important to notice that iOS can kill your process anytime after your
app is sent to background, so better to do it right after user´s
action.
Also, the user could not open your app again in a while.
You can subscribe to background capabilities for letting your app be active for a while longer while transitioning to suspended/close state if you are going to save data out of the iPhone.
The function you are looking for is applicationDidEnterBackground(_:)
Strong Points of this approach
You get last time that your app was actually used.
For more on the application life cycle and how to handle it correctly, please visit apple documentation about this topic
Do I need this information to be available between installs & Where to save ?
If you care about this counter to be stable and remains intact between installs you can not save it in any local database or NSUserDefaults. In this case you should implement some kind of online storage, via user creation & handling in your servers or the use of iCloud alternatives.
If your information is sensitive (let's say that you are going to give some money like reward to your user for opening your app 1000 times) then you can not store it in NSUserDefaults, as it is not encripted and can be modified.
What to save in order to count days in a row?
Simplicity is king when dealing with stored data and there are many ways to achieve this specific task.
I would go with:
Storing the first date (ignoring time if you are dealing with calendar days, but including it if you are handling 24hours lapses as your day instead)
Storing last visit date (same considerations apply).
You could save complete timestamp in order to be able of change your mind later ;-)
In my app I would do the maths then with current date data (now = NSDate()) before making any changes.
If timelapse between now and last visit date is bigger than
a "Day", then update first visit date with now.
Save now data into last visit date storage.
Your counter will always be the difference in "Days" between now and first visit date.
Summing Up
If your data is not sensitive store it in NSUserDefaults, otherwise and if this can affect your income store it somewhere else. If it's sensitive but you don't care if your user lose the counter, save it in a local DB (CoreData, Realm, etc)
Best time (as of my consideration) for storing new data will be when an intention of closure (included suspended state and incoming calls) is notified to your app.
You can save this data in many ways, one that give you some space for maneuvering is saving just last visit and date of first visit of the row and then do the maths. Of course, updating as needed and explained before.
private extension Sequence where Element == Date {
var streaks: Int {
let oneDayInSeconds: Double = 60*60*24
let days = self.compactMap { Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: $0) }
let uniq = Set(days).sorted(by: >)
var count = 0
guard var lastStreak = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date()) else { return count }
for date in uniq {
guard date > lastStreak.advanced(by: -oneDayInSeconds - 1) else { break }
count += 1
lastStreak = date
}
return count
}
}
Simply save the last date in your UserDefault or CoreData, then on the next date, use timeIntervalSinceDate to get the timeInterval, divide to 3600, if that value is more than 24, reset the number
I didn't checked but this idea will work, simply store current date (DD-MM-YYYY) into Array or list & save it using any Data Persistence Options. Before adding date (Whenever launching app - you can use didFinishLauchingOptions) into list check whether it had already or not.To showing continuous days use the array count of saved list.
To reset Zero just check last date is Available or not.
This is my implementation for keeping track of a login streak and if that streak was rewarded yet:
class func checkForStreak() -> Int {
let lastLogin = UserDefaults.standard.string(forKey: "lastLogin")
guard let lastLogin = lastLogin else {
UserDefaults.standard.set(1, forKey: "loginStreak")
UserDefaults.standard.set(Date().toString(), forKey: "lastLogin")
return 1
}
let format = DateFormatter()
format.dateFormat = "YYYY-MM-DD"
guard let lastLoginDate = format.date(from: lastLogin) else {
return 0
}
guard let modifiedDate = Calendar.current.date(byAdding: .day, value: 1, to: lastLoginDate) else {return 0}
if lastLoginDate.isToday {
//login on same day
return UserDefaults.standard.integer(forKey: "loginStreak")
} else if modifiedDate.isToday {
//streak is extended
var streak = UserDefaults.standard.integer(forKey: "loginStreak")
streak += 1
UserDefaults.standard.set(streak, forKey: "loginStreak")
UserDefaults.standard.set(false, forKey: "streakRewarded")
return streak
} else {
//streak is broken
UserDefaults.standard.set(1, forKey: "loginStreak")
return 1
}
}
you'll want to set:
UserDefaults.standard.set(true, forKey: "streakRewarded")
after rewarding the streak. I call this function in my viewDidLoad of the root view controller.
func streakCount() {
if USERDEFAULTS.value(forKey: "lastLogin") == nil{
USERDEFAULTS.set(1, forKey: "loginStreak")
USERDEFAULTS.set(Date().toString(), forKey: "lastLogin")
return
}
let lastLogin = USERDEFAULTS.value(forKey: "lastLogin") as! String
let format = DateFormatter()
format.dateFormat = "yyyy-MM-dd"
guard let lastLoginDate = format.date(from: lastLogin) else {
return
}
guard let modifiedDate = Calendar.current.date(byAdding: .day, value: 1, to: lastLoginDate) else {
return
}
if modifiedDate.isToday {
//streak is extended
var streak = USERDEFAULTS.integer(forKey: "loginStreak")
streak += 1
USERDEFAULTS.set(streak, forKey: "loginStreak")
USERDEFAULTS.set(Date().toString(), forKey: "lastLogin")
} else {
//streak is broken
let streak = USERDEFAULTS.integer(forKey: "loginStreak")
if USERDEFAULTS.value(forKey: "longestStreak") != nil{
let longestStreak = USERDEFAULTS.integer(forKey: "longestStreak")
let streak = USERDEFAULTS.integer(forKey: "loginStreak")
if streak > longestStreak{
USERDEFAULTS.set(streak, forKey: "longestStreak")
}
}
else{
USERDEFAULTS.set(streak, forKey: "longestStreak")
}
USERDEFAULTS.set(0, forKey: "loginStreak")
}
}
I am trying to add a timer for when a user clicks a button it starts a timer for 24 hours and disables the button for the next 24 hours. After that it is enabled again. There is a few answers out there for things similar but not 100% useful for doing it in SWIFT.
The main problem I am having is that I want this to be specific for each user. So 24 hours for every click on that one user. So for example: If I 'like' something then you want be able to 'like' that particular thing again for 24 hours but can still 'like' a different thing?
Thanks
*
Works for Swift 3
*
I have a daily video Ad that my users can view to get extra cash. This is what I use to ensure they can only view it once a day.
1.) Create a function that will be called when the user triggers it.
func set24HrTimer() {
let currentDate = NSDate()
let newDate = NSDate(timeInterval: 86400, since: currentDate as Date)
UserDefaults.standard.setValue(newDate, forKey: "waitingDate")
print("24 hours started")
//disable the button
}
2.) Create a variable at the top of your file.
let todaysDate = NSDate()
3.) In the viewDidLoad or didMoveToView call:
if let waitingDate:NSDate = UserDefaults.standard.value(forKey: "waitingDate") as? NSDate {
if (todaysDate.compare(waitingDate as Date) == ComparisonResult.orderedDescending) {
print("show button")
}
else {
print("hide button")
}
}
You can do it by setting the actual date + 1 day and save it into your NSUserDefaults:.
So in your button-pressed method, you can do something like that:
//user pressed button:
func buttonPressed(){
//current date
let currentDate = NSDate()
let calendar = NSCalendar.currentCalendar()
//add 1 day to the date:
let newDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: 1, toDate: currentDate, options: NSCalendarOptions.allZeros)
NSUserDefaults.standardUserDefaults().setValue(newDate, forKey: "waitingDate")
//disable the button
}
And to check the time you can retrieve the information. I would recommend to check it inside the AppDelegatemethods like applicationDidFinishLaunchingWithOptions.
//call it whereever you want to check if the time is over
if let waitingDate:NSDate = NSUserDefaults.standardUserDefaults().valueForKey("waitingDate") as? NSDate{
let currentDate = NSDate()
//If currentDate is after the set date
if(currentDate.compare(waitingDate) == NSComparisonResult.OrderedDescending){
//reenable button
}
}
A few things to consider first.
How important is it that this button can not be subverted?
If you are depending on the device for its current time and date, then the user can always just move that forward one day in the device settings.
Do you want any behavior to happen outside of your application?
should the user be notified that the button is now enabled
Assuming you don't need strictly enforce the 24 hour period, and you don't want to notify the user (they can find out when they return to your app), then you have only to do a few things.
Get a timeStamp when the button is pressed, start an NSTimer for 24Hours, and save the timeStamp to NSUserDefaults.
//Assuming you have a method named enableButton on self
let timer = NSTimer.scheduledTimerWithTimeInterval(86400, target: self, selector: "enableButton", userInfo: nil, repeats: false)
NSUserDefaults.standardUserDefaults().setObject(NSDate(), forKey: "timeStamp")
Now if the user never leaves your app, your good. In real life they will, so you will need to check when you re-enter your app if you need to disable the button, based on the timeStamp and start a new timer for the amount of time that is left.
I have run into a problem when trying to reload my UITableView from within my TimersManager.swift file. The TimersManager.swift is used to control/manage all the timers in my to-do list/timers app. I am trying to update the UILabel to show the updated time as the timer ticks away. For some reason it will not update the table. Please have a look below and hopefully you can give me a nudge in the right direction. Thanks.
top of listTableViewController.swift:
var prepMgr: listTableViewController = listTableViewController()
var cell:customCell!
class listTableViewController: UITableViewController, UITableViewDelegate, UITableViewDataSource {
update func in listTableViewController: (this is called by another func in my TimersManager.swift file)
func update (indexPathRow: Int) {
for task in taskMgr.tasks {
if task.timerOn == true {
//calculate the time to display in 0:00:00 format.
let date1 : NSDate = task.timerFinishDate
let date2 : NSDate = NSDate()
let compareResult = date1.compare(date2)
let length = Int(round(date1.timeIntervalSinceDate(date2)))
var tmpHours = length / 3600
var tmpMinutes = (length % 3600) / 60
var tmpSeconds = length % 60
var timeString = "\(tmpHours):\(tmpMinutes):\(tmpSeconds)"
println(task.subText) //test, display old value before update - WORKS
taskMgr.updateTask(indexPathRow, name: taskMgr.tasks[indexPathRow].name, subText: timeString, timerOn: taskMgr.tasks[indexPathRow].timerOn, completed: taskMgr.tasks[indexPathRow].completed, timerFinishDate: taskMgr.tasks[indexPathRow].timerFinishDate, taskID: taskMgr.tasks[indexPathRow].taskID, sliderHours: taskMgr.tasks[indexPathRow].sliderHours, sliderMinutes:taskMgr.tasks[indexPathRow].sliderMinutes, sliderSeconds: taskMgr.tasks[indexPathRow].sliderSeconds)
println(task.subText) //test, display updated value after update - WORKS
println(timeString) //test, display time remaining in timer 0:00:00 - WORKS
}
self.tableView.reloadData() // DOES NOT UPDATE TABLE.
}
}
the code for the NSTimer selector in TimersManager.swift:
func tickTock (length:NSTimer!) {
println(length.userInfo)
var count = 0
for timer in timers {
let date1 : NSDate = timer.fireDate
let date2 : NSDate = NSDate()
let compareResult = date1.compare(date2)
let length = Int(round(date1.timeIntervalSinceDate(date2)))
if length <= 0 {
//Invalidate NSTimer
timer.myTimer.invalidate()
//Remove from array
timers.removeAtIndex(count)
}
count++
println(length) //test, shows how many seconds are left - WORKS
//update labels.
prepMgr.update(timer.indexPathRow) //Call to listTableViewController func - Half working, calls the function. updates the correct task. But table is not reloaded.
}
//update labels, reload table
prepMgr.tableView.reloadData() //Test, Not working
}
You could also use a NSNotification to handle the "Reload Function" of the Table. And just call them if you need an update of your table.