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.
Related
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 have an NSDate value. I need to check (compare to system current time) if that is yesterday or not. I thought that was easy because I could just pull the day value out of my NSDate and +1 to compare it. But soon afterward, I realized it's an inappropriate idea because what if it's end of the month, let's say July 31. And next day is not July 32, is August 1.
What's the most effective way to check if an NSDate is yesterday (compare to current time)?
As of iOS 8.0, you can use -[NSCalendar isDateInYesterday:], like this:
let calendar = NSCalendar.autoupdatingCurrentCalendar()
let someDate: NSDate = some date...
if calendar.isDateInYesterday(someDate) {
// It was yesterday...
}
If you'll be doing this a lot, you should create the calendar once and keep it in an instance variable, because creating the calendar object is not trivial.
In Swift 3/4/5 the API has changed:
Calendar.current.isDateInToday(yourDate)
Calendar.current.isDateInYesterday(yourDate)
Calendar.current.isDateInTomorrow(yourDate)
To get the current date:
let now = Date()
Here is an extension in Swift to check if the date is past date.
extension Date {
var isPastDate: Bool {
return self < Date()
}
func isYesterday() -> Bool {
return Calendar.current.isDateInYesterday(self)
}
}
Usage:
let someDate = Date().addingTimeInterval(1)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
print(date.isPastDate)
}
This question already has answers here:
How can I make a countdown timer like in a music player?
(2 answers)
Closed 7 years ago.
I'm trying to make an app that tell us the rest of time from the present time till one hour later.
This is the code but now it only has a function that tell us the countdown time by decreasing one second from the present time.
I'm thinking that I haven't definite the definition of the "cnt"
so that's why I'm thinking it doesn't work.
Can somebody tell me the reason and a solution?
import UIKit
class ViewController: UIViewController {
var cnt : Int = 0
var timer : NSTimer!//NSTimerというデフォルト機能から引っ張る
var myInt:Int = 0
override func viewDidLoad() {
let myDate: NSDate = NSDate()
let myCalendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let myComponents = myCalendar.components([.Year, .Hour, .Minute, .Second],
fromDate: myDate) // myDate、すなわちNSDateから要素として引っ張り出してる
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "onUpdate:", userInfo: nil, repeats: true)//カウントダウンのインターバル
timer.fire()
var myStr: String = "\(myComponents.hour)"
myStr += "\(myComponents.minute)"
myStr += "\(myComponents.second)"
myInt = Int(myStr)! // toInt()がSwift2より無効になったようです。myInt=Str(my components,hour,minute,second)=現時刻
}
func onUpdate(timer : NSTimer){
cnt += 1//cnt+1=cnt,
let count = myInt - cnt //残り時間=現在時刻ー現在時刻に1時間足した時刻
print(count) // println()は、Swift2よりDeprecatedになりました。
}
}
It is difficult to understand what you're asking, but I will do my best.
In your viewDidLoad method, you're setting myInt to the integer representation of myStr. If the time is 18:30:50, myInt will be equal to 183050. That is not an appropriate representation of the time. Time is base 60, integers are base 10, for one thing. If you want to represent time as a single number, you can use timeIntervalSinceDate, or timeIntervalSinceReferenceDate or timeIntervalSince1970 to get the NSTimeInterval (ie. fractional seconds) representation of the date relative to a certain epoch either of your choosing or one built into Foundation.
Subtracting 1 from myInt each time the timer fires isn't going to give you an indication of the time remaining.
Also, NSTimer is not an accurate way to keep time. You should instead save the start date as a property and determine the time remaining based on timeIntervalSinceDate
e.g.
func onUpdate(timer : NSTimer){
let currentTime = NSDate()
let timeElapsed = currentTime.timeIntervalSinceDate(myDate)
println(timeElapsed)
}
If you want to show time elapsed in minutes, you can divide it by 60. You can look into NSDateComponentsFormatter to easily get a string representation of time intervals.
If you want the countdown to stop after an hour, then check for when timeElapsed is over 3600.
If you want it to show a countdown from 1 hour, then subtract the timeElapsed from 3600.
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()
}
I need to do some time math in iOS, with Swift.
I have to use dispatch_walltime. I hope that can be taken as axiomatic. Where time math is concerned, I think I'm likely to get the response "just use NSDate," but please take it on faith: I am bound to dispatch_walltime.
Now, it's plain why someone might suggest NSDate, because when you're using NSTimeInterval and NSDate and that good stuff, it's pretty easy to make custom timestamps and compare them and do all kinds of time math.
But I have to use dispatch_time_t, and specifically dispatch_walltime created like this:
//Get the timeInterval of now.
let nowInterval = NSDate().timeIntervalSince1970
//Make a timespec from it.
var nowStruct = timespec(tv_sec: Int(nowInterval), tv_nsec: 0)
//Make a walltime definition from that.
let referenceWalltime = dispatch_walltime(&nowStruct, 0)
Later on I need to use that reference time in various ways. For instance, I need to get the time interval between the reference time and whatever time it happens to be.
I am attempting to do this the same way I would with NSTimeInterval, in other words, make a new one and subtract the old one from it:
//Repeat everything from before to make a new wall time.
let newNowInterval = NSDate().timeIntervalSince1970
var newNowStruct = timespec(tv_sec: Int(newNowInterval), tv_nsec: 0)
let newWalltime = dispatch_walltime(& newNowStruct, 0)
//Time math a la NSTimeInterval to find the interval:
let walltimeInterval = newWalltime - referenceWalltime
Will that work?
The short answer is: no. That code will crash.
The better answer is: no, but it can be done, and it's not all that different in the end.
I did some investigating on my own in a Playground and learned some interesting things, and I believe I figured out the right way to do this.
I'm pasting the entirety of my Playground here, so that others can copy-paste it and figure out how to do their own dispatch_time math.
Comments marked by //********* in the code denote the key things I learned.
import UIKit
import XCPlayground
XCPSetExecutionShouldContinueIndefinitely(continueIndefinitely: true)
public class IntervalMaker {
var referenceWalltime: dispatch_time_t = 0
var newWalltime: dispatch_time_t = 0
var walltimeInterval: dispatch_time_t = 0
func scheduleWalltimeSequence () {
let threeSeconds = Int64(NSEC_PER_SEC) * 3
let now = walltimeNow()
let dispatchTimeInThree = dispatch_time(now, threeSeconds)
let dispatchTimeInSix = dispatch_time(now,
2 * threeSeconds)
setReferenceWalltimeToNow()
dispatch_after(dispatchTimeInThree, dispatch_get_main_queue(),
setNewWalltimeToNow)
dispatch_after(dispatchTimeInSix,
dispatch_get_main_queue(), dispatchBasedOnDispatchMath)
}
func walltimeNow()->dispatch_time_t{
let nowInterval = NSDate().timeIntervalSince1970
var nowStruct = timespec(tv_sec: Int(nowInterval), tv_nsec: 0)
return dispatch_walltime(&nowStruct, 0)
}
func setReferenceWalltimeToNow () {
referenceWalltime = walltimeNow()
}
func setNewWalltimeToNow (){
newWalltime = walltimeNow()
}
func dispatchBasedOnDispatchMath () {
computeInterval() //Should be three seconds
let nineTheWrongWay = referenceWalltime + (walltimeInterval * 3)
let nineTheRightWay = dispatch_time(referenceWalltime,
Int64(walltimeInterval) * 3)
dispatch_after(nineTheWrongWay,
dispatch_get_main_queue(), finalPrintln)
//********** THE ABOVE DOES NOT WORK CORRECTLY - prints 6 seconds later
dispatch_after(nineTheRightWay,
dispatch_get_main_queue(), finalPrintln)
//********** THE ABOVE WORKS CORRECTLY - prints 9 seconds later
}
func finalPrintln () {
let now = walltimeNow()
println("I should be printing nine seconds from reference time.")
println("It's actually \(referenceWalltime - now) nanoseconds after")
}
func computeInterval () {
walltimeInterval = referenceWalltime - newWalltime
//********** dispatch_walltimes actually count backwards, and *CANNOT* be
//********** negative: writing `newWalltime - referenceWalltime` will crash
}
}
let intervaller = IntervalMaker()
intervaller.scheduleWalltimeSequence()