I am currently learning to code swift apps and am trying to do some projects on my own. My current personal challenge is to do a countdown timer app. The concept is simple: the user enters a date, from a UIDatePicker, and the app shows the time remaining until his list of various events (uses user default values to keep the events in memory). All the said events are shown in a collection view (see below for screen shots).
I ran into something too complicated for my actual skillset and I thought you guys probably would have the answer, or at least a few suggestions! Here is my issue: I'd like for the time remaining between today and the event to decrease every second, and to be shown through a label inside a collectionViewCell. I found a very simple way to do so, with a timer, but implementing the said solution with a collectionViewCell is giving me quite the headache.
Here are the code excerpts I'd like to share, and hopefully it's enough:
#objc func UpdateTimeLabel(index: Int) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = userCalendar.timeZone
let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: date)
return "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
}
That's my function I'd like to fire every second. It is currently made in such a way that its property, called index, is the indexPath.row of the collectionViewCell. It references to an array of event dates stored as UserDefaults. There's as many cells in the collectionView as there is events in the array.
See below the way I implemented it inside the collectionView forItemAt function:
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
// Configure the state of the cell based on the propertires of the card if reprensents
let cardCell = cell as? EventCollectionViewCell
// UI Configuration
cardCell?.eventTitleLabel.text = ((UserDefaults.standard.array(forKey: "events")![indexPath.row]) as! String)
cardCell?.eventDateLabel.text = ("Event on: " + "\((UserDefaults.standard.array(forKey: "dates")![indexPath.row]) as! String)")
cardCell?.countdownLabel.text = UpdateTimeLabel(index: indexPath.row)
cardCell?.eventView.layer.cornerRadius = 10
}
The actual result is that every cell shows the time remaining between today and the event. That's already more than I thought I could do by myself!!!
Results of actual code
Where I think one of you can probably step-in is by helping me answering this question: Where should a timer, looking something like the code below, be placed in order for the cardCell.countdownLabel.text to be updated every second?
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
I tried every ways I can think of and am now at a road block. I still have many more things to fix in my project, so I'm not completely stopped yet. If you need more code lines and/or precisions, let me know and I'll provide you with anything you deem helpful.
Thank you so much for taking the time to review my question,
Louis G
Add this method UpdateTimeLabel method into the EventCollectionViewCell and modify method like this
#objc func UpdateTimeLabel() {
let userCalendar = Calendar(identifier: .indian)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = userCalendar.timeZone
let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: Date(), to: date)
countdownLabel.text = "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
}
After doing this add a property index in EventCollectionViewCell and on didSet of index fire timer in EventCollectionViewCell eg:
var index: Int {
didSet {
let timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
timer.fire()
}
}
Replace
cardCell?.countdownLabel.text = UpdateTimeLabel(index: indexPath.row)
with
cardCell?.index = indexPath.row
I found why the solution #Satyen suggested didn't work! Ok I know, it's been almost a month since his last answer, but I have to say I got pretty discouraged when I saw it didn't fix my app and decided to work on other projects, hoping someone else would jump in and offer another solution in the meanwhile. Anyways, I decided this morning to find the issue through a few sessions, and I'm proud to report I did!
Ok so here's what went wrong:
I was using a variable called "current date" as today's. This very variable was declared outside the UpdateTimeLabel function, and transformed in dateComponents right of the bath. Result: the current date wasn't refreshed every second, so every time the event's date was compared to currentDate, the result stayed the same.
Here is the working code:
#objc func UpdateTimeLabel() {
let userCalendar = Calendar.current
let todayDate = Date()
let components = userCalendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: todayDate)
let currentDate = userCalendar.date(from: components)!
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
dateFormatter.timeZone = userCalendar.timeZone
let date = dateFormatter.date(from: UserDefaults.standard.array(forKey: "dates")![index] as! String)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: date)
countdownLabel.text = "\(timeLeft.day!) days \(timeLeft.hour!) hours \(timeLeft.minute!) minutes \(timeLeft.second!) seconds"
if currentDate >= date {
countdownLabel.text = "Cheers! This event has occured already."
print("An event expired.")
}
}
Fired by this var index, as suggested by #Satyen:
var index: Int = 0 {
didSet {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTimeLabel), userInfo: nil, repeats: true)
timer.fire()
print("timer fired")
}
}
Everything is now working like expected! I still have a few efficiency tweaks to make my code simpler, but overall it is now displaying the remaining time and decreasing it every second! So excited!!
Thank you to #Satyen once again for his precious input!
Im new to programming, so I would be very happy if somebody could help me 😅
I want to create a calculator which calculates the seconds which have passed between a certain time (i. e. „03:30“) and the current time (when the Button is clicked).
For example:
Selected time = „00:30“ (in TextField)
Time when Button is clicked = 04:00
Output (seconds passed) = 1800
You can have current hour, minute and seconds according to your timezone.
I would use like this.
var myCalendar = Calendar.current
var date1 = Date()
func viewDidLoad(){
super.viewDidLoad()
date1 = Date()
}
#IBAction func buttonPressed(_ sender: Any) {
let date2 = Date();
let hour = myCalendar.component(.hour, from: date2)
let minute = myCalendar.component(.minute, from: date2)
let second = myCalendar.component(.second, from: date2)
print("Time when Button is clicked: "\(hour)":"\(minute))
print("Seconds passed: "\(date2.timeIntervalSinceNow-date1.timeIntervalSinceNow))
}
I made a simple count down app and I get this wrong message: cannot call value of non-function type "calendar".
What I need to change?
#IBOutlet weak var deteLabelOutlet: UILabel!
let formatter = DateFormatter()
let useCalendar = NSCalendar.current()
let requesedComponent: NSCalendar.Unit = [
NSCalendar.Unit.month,
NSCalendar.Unit.day,
NSCalendar.Unit.hour,
NSCalendar.Unit.minute,
NSCalendar.Unit.second
]
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(printTime), userInfo: nil, repeats: true)
timer.fire()
}
func printTime() {
formatter.dateFormat = "MM/dd/yy hh:mm:ss a"
let startTime = Date()
let endTime = formatter.date(from: String("12/25/16 12:00:00 a"))
let timeDiffrenece = useCalendar.components(requesedComponent,fromDate: startTime, toDate: endTime!, options: [] )
deteLabelOutlet.text = "\(timeDiffrenece.month)Month \(timeDiffrenece.day) days \(timeDiffrenece.minute) minutes \(timeDiffrenece.second) seconds"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
In Swift 3 the current calendar is not a function. Remove the parentheses.
Anyway it's highly recommended to use the native struct Calendar as suggested in the other answer
let useCalendar = Calendar.current
Edit:
You have to unwrap all date components when assigning the values to the label because in Swift 3 all date components are optional. However as all relevant components are specified you can forced unwrap the components safely.
deteLabelOutlet.text = "\(timeDiffrenece.month!)Month \(timeDiffrenece.day!) days \(timeDiffrenece.minute!) minutes \(timeDiffrenece.second!) seconds"
I´m assembling an application with regular web updates. Every update leaves an unix time stamp. Now i try to create an editor, which shows the elapsed time since last update. I´m convering the unix time stamp with this code:
import Cocoa
struct UnixConverter {
static func converUnix(_ timestamp: Int) -> NSDate? {
let date = NSDate(timeIntervalSince1970: TimeInterval(timestamp))
return date
}
}
Now I´m getting a time in this format: "2017-06-16 17:47:45 +0000"
The following function gives me a reals time push update inside a NSTextFieldCell:
import Cocoa
class ViewController: NSViewController {
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
// This function pushes the time NSTextFieldCell
timer = Timer.scheduledTimer(timeInterval: 1.0,
target: self, s
elector: #selector(tick),
userInfo: nil,
repeats: true)
}
// This is the NSTextFieldCell formatter/ receiving container
#objc func tick() {
timerTextField.stringValue = DateFormatter.localizedString(from: NSDate() as Date, dateStyle: .medium, timeStyle: .medium)
}
}
Now "all" I would have to do, is to make a subtraction to get the difference. At this point I must confess I don´t have any idea how to do this. After that the time difference has to be pushed inside the NSTextFieldCell.
I would be very glad if someone could give me a hint how to do it.
How can I create a user defined countdown timer in Swift?
I got 2 pages in my app. On first page I ask user to write in UITextField a value as hour (for ex: 3), then with segue.identifier, I pass this information to second page, and I want to make a countdown from 3 hours as 02:59:59 format and print it in UILabel. Thanks
I made you a playground with an example of countdown with a NSTimer.
import UIKit
class MyClass {
var timerCounter:NSTimeInterval!
func stringFromTimeInterval(interval: NSTimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60) % 60
let hours = (interval / 3600)
return String(format: "%02d:%02d:%02d", hours, minutes, seconds)
}
func startTimer(hour:Int) {
timerCounter = NSTimeInterval(hour * 60 * 60)
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "onTimer:", userInfo: nil, repeats: true)
}
#objc func onTimer(timer:NSTimer!) {
// Here is the string containing the timer
// Update your label here
println(stringFromTimeInterval(timerCounter))
timerCounter!--
}
}
var anInstance = MyClass()
// Start timer with 3 hours
anInstance.startTimer(3)
CFRunLoopRun()