I Want My swift code to count down to the nearest top of the hour. So if the time is 146 the user code should count down 14 minutes. Right now My code below counts down to a spefic day and time. I just want it to count down to the nearest hour when the app is running.
import UIKit
class ViewController: UIViewController {
#IBOutlet var timerLabel: UILabel!
var timer: Timer!
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(UpdateTime), userInfo: nil, repeats: true)
}
#objc func UpdateTime() {
let userCalendar = Calendar.current
// Set Current Date
let date = Date()
let components = userCalendar.dateComponents([.hour, .minute, .month, .year, .day], from: date)
let currentDate = userCalendar.date(from: components)!
// Set Event Date
var eventDateComponents = DateComponents()
eventDateComponents.year = 2021
eventDateComponents.month = 01
eventDateComponents.day = 01
eventDateComponents.hour = 01
eventDateComponents.minute = 00
eventDateComponents.timeZone = TimeZone(abbreviation: "GMT")
let eventDate = userCalendar.date(from: eventDateComponents)!
let timeLeft = userCalendar.dateComponents([.day, .hour, .minute, ], from: currentDate, to: eventDate)
timerLabel.text = "\(timeLeft.day!)d \(timeLeft.hour!)h \(timeLeft.minute!)m "
endEvent(currentdate: currentDate, eventdate: eventDate)
}
func endEvent(currentdate: Date, eventdate: Date) {
if currentdate >= eventdate {
timerLabel.text = "Happy New Year!"
// Stop Timer
timer.invalidate()
}
}
}
edit/update:
My goal in my swift code is when the top of the hour is reached. After trying to implement #Leo's answer it prints "Top of Hour" and it does the problem is that It only does it one time. As long as the app is open I want it to print "Top of Hour" at every hour. So I need to reset the end date which is what I tried to do at
let date = Date()
end = date.nextHour
That does not let the code compile. So I have to reset the end var to the next hour.
No need to update the user interface 10 times per second. As it is it will drain the device's battery much faster than needed while it should only run once a minute. You can change your timer timeInterval to 1 second and schedule it to fire at the next even second. To get the next even hour and the next even minute you can use Calendar method
func nextDate(after date: Date, matching components: DateComponents, matchingPolicy: Calendar.MatchingPolicy, repeatedTimePolicy: Calendar.RepeatedTimePolicy = .first, direction: Calendar.SearchDirection = .forward) -> Date?
Just create two computed properties extending Date and pass zero for minute or nanosecond components:
extension Date {
var nextHour: Date {
Calendar.current.nextDate(after: self, matching: DateComponents(minute: 0), matchingPolicy: .strict)!
}
var nextSecond: Date {
Calendar.current.nextDate(after: self, matching: DateComponents(nanosecond: 0), matchingPolicy: .strict)!
}
var minute: Int {
Calendar.current.component(.minute, from: self)
}
}
Now add a property to your view controller to keep a reference of the end date. Note that there is no need to declare your timer as optional:
var end: Date?
var timer = Timer()
And create a DateComponentsFormatter to create a localized description of the remaining time:
extension Formatter {
static let minutesRemaining: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.formattingContext = .standalone
formatter.unitsStyle = .short
formatter.allowedUnits = [.minute, .second]
formatter.includesTimeRemainingPhrase = true
return formatter
}()
}
Now you just setup the end date and to schedule your timer to fire at the next even minute:
override func viewDidLoad() {
super.viewDidLoad()
// get the current date
let date = Date()
// set the end date
end = date.nextHour
// schedule the timer to fire at the next even second and set its interval to 1 second
timer = .init(fireAt: date.nextSecond, interval: 1, target: self, selector: #selector(updateUI), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
updateUI()
}
#objc func updateUI() {
if Date().minute == 0 || Date() > end {
end = Date().nextHour
timerLabel.text = "beginning of hour"
print("beginning of hour")
} else {
// update the remaining time (for a perfect sync we need to subtract a second from the current time)
let text = Formatter.minutesRemaining.string(from: Date().addingTimeInterval(-1), to: end) ?? ""
timerLabel.text = text
print(text)
}
}
Related
I have added in a UIPickerView and currently have it store the selected time as a string. I want the app to carry out a simple line of code when the time that was selected on the pickerview is the time in the real world. Here is the code that I have added.
For the Clock, used to find the real world time:
let clockString: String = formatADate()
func formatADate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "hh:mm:ss a"
let date = NSDate()
let output = dateFormatter.string(from: date as Date)
print(output)
return output
}
Here is the code for the UIPickerView:
#IBOutlet var dateTimeDisplay: UILabel!
#IBOutlet var datePicker: UIDatePicker!
#IBAction func datePickerchanged(_ sender: Any) {
setDateAndTime()
}
func setDateAndTime() {
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm:ss a"
_ = formatter.string(from: datePicker.date)
str = dateFormatter.string(from: (datePicker?.date)!)
dateTimeDisplay.text = str
}
And here is what I want to happen when the selected time and the real world time match up:
takePhoto = true
When the pick date the start one timer function
call the function in picker
var timercount = Timer()
viewdidload()
{
timercount = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(Check), userInfo: nil, repeats: true)
}
Func Check()
{
let nowdate = NSDate()//two date declare global Var
let date2 = datePicker?.date //chek the Time How Much Time Remain
let elapsed = date2?.timeIntervalSince(nowdate as Date)
if Int(elapsed!) == 0
{
takePhoto = true
}
}
#IBOutlet var timeLabel: UILabel!
var timer = Timer()
let now = Date()
let userCalendar = Calendar.current
let formatter = DateFormatter()
func printTime() {
formatter.dateFormat = "MM/dd/yy hh/mm/ss a"
let startTime = now
let endTime = formatter.date(from: "01/01/17 00/00/00 a")
let difference = userCalendar.dateComponents([.month, .day, .year, .hour, .minute, .second], from: startTime, to: endTime!)
timeLabel.text = "Yılbaşına \(difference.month!) ay, \(difference.day!) gün, \(difference.hour!) saat, \(difference.minute!) dakika, \(difference.second!) saniye kaldı"
print("hello world")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
timer = Timer.scheduledTimer(timeInterval: 1,
target: self,
selector: #selector(ViewController.printTime),
userInfo: nil,
repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
When it is running
I am using Xcode 8 with Swift 3. When I run this code it is writes to console "hello world" every second but it is not update label when timer is running. I want to countdown for label for every second.
Try this:
DispatchQueue.main.after(when: .now() + 1.0) { //after one second
timeLabel.text = "Yılbaşına \(difference.month!) ay, \(difference.day!) gün, \(difference.hour!) saat, \(difference.minute!) dakika, \(difference.second!) saniye kaldı"
}
I solved. First I delete "let now = Date()" line and I changed "let startTime = Date()
I'm working in swift and want to run a function every minute. I want to update a label with a count down timer with how many minutes left till the next update.
I have a basic version working
if let date = newDate {
let formatter : NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "HH:mm"
formatter.timeZone = NSTimeZone.defaultTimeZone()
let string : NSString = formatter.stringFromDate(date)
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: date)
let minute = comp.minute
let remaining : Int = 60 - minute
var mins = "s"
if remaining == 1 {
mins = ""
}
self.refreshInLabel.text = "Refreshes at \(string) - \n \(remaining) minute\(mins) remaining "
}
which is updating when i view the page on the app, but i want it to auto update every minutes.
I've looked at NSTimer, i believe it can be done with this (as shown here: How to make a countdown with NSTimer on Swift) but i can't work out how to make it fire on the minute, only after a certain time display
Edit:
I have the following so far
override func viewDidLoad() {
super.viewDidLoad()
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: NSDate())
minute = comp.minute
_ = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(AdoptionCentreVC.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer() {
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: NSDate())
let curMin = comp.minute
if(curMin > minute) {
NSLog("Changed")
self.minute = curMin
}
}
I'd like to know if theres a better way
What I suggest you is to fire a NSNotification when you want to start your func every minute.
When you receive your NSNotification call a function like this :
var yourTimer = NSTimer()
func callWhenNotificationReceived(){
yourFuncToFire()
yourTimer = NSTimer.scheduledTimerWithTimeInterval(60, target: self, selector: #selector(YourViewController.yourFuncToFire) , userInfo: nil, repeats: true)
}
var TotalTime:Int = 0
var timer: NSTimer?
triggerCountDownTimerFor(time:Int)
func triggerCountDownTimerFor(time:Int)
{
totalTime = time
timer = NSTimer.scheduledTimerWithTimeInterval(1, target:self, selector: #selector(LoginViewController.updateTimer), userInfo: nil, repeats: true)
}
func updateTimer()
{
let date = NSDate()
let formatter : NSDateFormatter = NSDateFormatter()
formatter.dateFormat = "HH:mm"
formatter.timeZone = NSTimeZone.defaultTimeZone()
let string : NSString = formatter.stringFromDate(date)
let calendar = NSCalendar.currentCalendar()
let comp = calendar.components([.Minute], fromDate: date)
let minute = comp.minute
let remaining : Int = TotalTime - minute
var mins = "s"
if remaining == 1 {
mins = ""
}
print("Refreshes at \(string) - \n \(remaining) minute\(mins) remaining ")
}
I have a function to work out the start and end of a week which works as expected. I want to implement another function which works out the start and end of a single day. I have the code below however I get the following error:
Type of expression is ambiguous without more context.
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func calcStartAndEndDateForWeek(durationOccurance: Double) {
print("Calculating start and end for week")
let componentsWeek = calendar.components([.YearForWeekOfYear, .WeekOfYear], fromDate: date)
let startOfWeek = calendar.dateFromComponents(componentsWeek)!
print("start of Week = \(dateFormatter.stringFromDate(startOfWeek))")
let componentsWeekEnds = NSDateComponents()
componentsWeekEnds.weekOfYear = 1
let endOfWeek = calendar.dateByAddingComponents(componentsWeekEnds, toDate: startOfWeek, options: [])!
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay(durationOccurance: Double) {
print("Calculating start and end for day")
let componentsWeek = calendar.components([.Minutes, .Seconds], fromDate: date)
let startOfDay = calendar.dateFromComponents(componentsWeek)!
print("start day = \(dateFormatter.stringFromDate(startOfDay))")
}
init(){
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
We can create a more generic function using the methods on NSCalendar:
func rangeOfPeriod(period: NSCalendarUnit, date: NSDate) -> (NSDate, NSDate) {
let calendar = NSCalendar.currentCalendar()
var startDate: NSDate? = nil
// let's ask calendar for the start of the period
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
// end of this period is the start of the next period
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
// you can subtract 1 second if you want to make "Feb 1 00:00:00" into "Jan 31 23:59:59"
// let endDate2 = calendar.dateByAddingUnit(.Second, value: -1, toDate: endDate!, options: [])
return (startDate!, endDate!)
}
Called as
print("\(rangeOfPeriod(.WeekOfYear, date: NSDate()))")
print("\(rangeOfPeriod(.Day, date: NSDate()))")
Putting it into your code:
public class Date {
let dateFormatter = NSDateFormatter()
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
func rangeOfPeriod(period: NSCalendarUnit) -> (NSDate, NSDate) {
var startDate: NSDate? = nil
calendar.rangeOfUnit(period, startDate: &startDate, interval: nil, forDate: date)
let endDate = calendar.dateByAddingUnit(period, value: 1, toDate: startDate!, options: [])
return (startDate!, endDate!)
}
func calcStartAndEndDateForWeek() {
let (startOfWeek, endOfWeek) = rangeOfPeriod(.WeekOfYear)
print("Start of week = \(dateFormatter.stringFromDate(startOfWeek))")
print("End of the week = \(dateFormatter.stringFromDate(endOfWeek))")
}
func calcStartAndEndDateForDay() {
let (startOfDay, endOfDay) = rangeOfPeriod(.Day)
print("Start of day = \(dateFormatter.stringFromDate(startOfDay))")
print("End of the day = \(dateFormatter.stringFromDate(endOfDay))")
}
init() {
dateFormatter.dateFormat = "dd-MM-yyyy"
}
}
let myDate = Date()
myDate.calcStartAndEndDateForWeek()
myDate.calcStartAndEndDateForDay()
I was implementing something similar and went the following route:
extension Date {
static var startOfToday: Date? {
let date = Date()
guard !date.isStartOfDay else { return date }
return date
.zero(out: .second)?
.zero(out: .minute)?
.zero(out: .hour)?
.addingTimeInterval(-24 * 60 * 60)
}
private func zero(out: Calendar.Component) -> Date? {
return Calendar.current
.date(bySetting: out, value: 0, of: self)
}
private var isStartOfDay: Bool {
let cal = Calendar.current
let hours = cal.component(.hour, from: self)
let minutes = cal.component(.minute, from: self)
let seconds = cal.component(.second, from: self)
return hours == 0 && minutes == 0 && seconds == 0
}
}
Setting a component to zero will increment the next bigger component. So just setting the hour to zero will push the date to the next day at 00:00, unless of course the hour is already at zero. So to make it work for any date we have to zero out the seconds, minutes and hours (in that order). And to make sure we don't end up at the beginning of yesterday we first check if all values aren't already at zero.
I realize this is kinda hacky and probably not the best way to go about this, but it seems to work well enough for my use-case at least.
Getting the end of the day can be built on top of this by just adding another day.
Anyone know how i can solve this? I get this error:
¨cannot convert value of type 'NSDate' to expected argument type 'NSDateComponents'¨ on line:
The error occurred at the line:
let competitionDay = userCalendar.dateFromComponents(competitionDate)!
This is a more complete excerpt of the code:
func Date() {
// Here we set the current date
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Hour, .Minute, .Second, .Nanosecond], fromDate: date)
let hour = components.hour
let minutes = components.minute
let month = components.month
let year = components.year
let day = components.day
let currentDate = calendar.dateFromComponents(components)
// here we set the due date. When the timer is supposed to finish
// final Calendar value
let userCalendar = NSCalendar.currentCalendar()
let competitionDate:NSDate = myDatePicker.date
competitionDate.timeIntervalSinceNow
let competitionDay = userCalendar.dateFromComponents(competitionDate)!
// Here we compare the two dates
competitionDay.timeIntervalSinceDate(currentDate!)
let dayCalendarUnit = calendar.components([NSCalendarUnit.Day, NSCalendarUnit.Hour, NSCalendarUnit.Minute], fromDate: date)
//here we change the seconds to hours,minutes and days
let competitionDayDifference = calendar.components([.Day, .Hour, .Minute],
fromDate: currentDate!, toDate: competitionDay, options: NSCalendarOptions())
//finally, here we set the variable to our remaining time
let daysLeft = competitionDayDifference.day
let hoursLeft = competitionDayDifference.hour
let minutesLeft = competitionDayDifference.minute
In your original question you were calling dateFromComponents, which converts a NSDateComponents to a NSDate, but supplied it a competitionDate, which is already a NSDate. And the compiler was simply informing you of this error.
--
As an aside, you populate a NSDateComponents with the .Hour, .Minute, .Second, and .Nanosecond from a NSDate, but then proceed to try to save references to the components.year and components.month and components.day even though you didn't specify that those components should be included in the NSDateComponents.
--
Below you say:
It's a Countdown app and i had it running until i wanted a DatePicker to choose the date i am counting down. The competitionDate is the due date
If that's indeed what you want, then perhaps NSDateComponents isn't needed at all. I'd probably just use NSDateComponentsFormatter method stringFromDate:toDate: to show the user a nice string representation of the amount of time between two NSDate objects.
For example:
class ViewController: UIViewController {
#IBOutlet weak var datePicker: UIDatePicker!
#IBOutlet weak var label: UILabel!
weak var timer: NSTimer?
let formatter: NSDateComponentsFormatter = {
let _formatter = NSDateComponentsFormatter()
_formatter.allowedUnits = [.Year, .Month, .Day, .Hour, .Minute, .Second]
_formatter.unitsStyle = .Full
return _formatter
}()
override func viewDidLoad() {
super.viewDidLoad()
datePicker.date = NSDate().dateByAddingTimeInterval(1000) // initialize it to whatever you want
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
timer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: "didFireTimer:", userInfo: nil, repeats: true)
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate()
}
func didFireTimer(timer: NSTimer) {
label.text = formatter.stringFromDate(NSDate(), toDate: datePicker.date)
}
}