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 ")
}
Related
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)
}
}
I have a UIDatePicker with the mode time. The date picker lets you pick an hour and minute. I need it to remember the hour and minute not the seconds.
When the app is running the date picker will take the hours and minute the user selected and add the seconds from the time you took to the remembered time.
An example when I select 2:00 pm from the UIDatePIcker this is what I get:
2017-03-07 02:00:36 +0000
However i want to either set the seconds to 00 or remove them like this:
2017-03-07 02:00 +0000
2017-03-07 02:00:00 +0000
Here is my code:
#IBOutlet var datePicker: UIDatePicker!
#IBAction func datePickerchanged(_ sender: Any) {
setDateAndTime()
Check()
let clockString: String = formatADate()
if str == clockString{
takePhoto = true
}
}
func setDateAndTime() {
timercount = Timer.scheduledTimer(timeInterval: 0, target: self, selector: #selector(Check), userInfo: nil, repeats: true)
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm"
_ = formatter.string(from: datePicker.date)
str = dateFormatter.string(from: (datePicker?.date)!)
RunLoop.main.add(timercount, forMode: RunLoopMode.commonModes)
print("setdate ran")
Check()
}
func formatADate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .short
dateFormatter.dateFormat = "hh:mm"
let date = NSDate()
let output = dateFormatter.string(from: date as Date)
print(output)
return output
}
func Check(){
let nowdate = NSDate()
let date2 = datePicker?.date
let elapsed = date2?.timeIntervalSince(nowdate as Date)
print(str)
print(Date())
print(datePicker.date)
if Int(elapsed!) == 0{
takePhoto = true
}
Please help
To modify Date, I prefer to use https://github.com/MatthewYork/DateTools.
let dateWithZeroSeconds = Date(year:datePicker.date.year, month:datePicker.date.month, day:datePicker.date.day, hour:datePicker.date.hour, minute:datePicker.date.minute, second:0)
You could also do a similar transform with DateComponents, but DateTools is a very nice wrapper around all of that.
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
}
}
I am using cvcalendar and there is in everyday a different times for fajer, dohor , aser , maghreb , ishaa . for example i have selected the Adan for Fajer, so i want to get the adan in everyday and everyday has a different time. so when i get a notification in DidreceivedLocalNotification i want go to next day in calendar and get the time of the next day, knowing that am getting the times from CoreData .
in viewWillappear
let date = NSDate()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
let calendar = NSCalendar.currentCalendar()
let calendarForDate = NSCalendar.currentCalendar()
let componentsForDate = calendar.components([.Day , .Month , .Year], fromDate: date)
let year = componentsForDate.year
let month = componentsForDate.month
let day = componentsForDate.day
//////////
//// Conditions after selecting the user (vibration, beep, Adan ) these conditions are testing the selected choice to send a notification to the user on his choice
//
if prayerCommingFromAdan.id == 0 && prayerCommingFromAdan.ringToneId != 0{
notificationId.id = 0
let hours = prayer0.time[0...1]
let minutes = prayer0.time[3...4]
let fajerTime = "\(month)-\(day)-\(year) \(hours):\(minutes)"
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy hh:mm"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
// convert string into date
let dateValue = dateFormatter.dateFromString(fajerTime) as NSDate!
var dateComparisionResult:NSComparisonResult = NSDate().compare(dateValue)
if dateComparisionResult == NSComparisonResult.OrderedAscending
{
addNotificationAlarm(year, month: month, day: day, hour: prayer0.time[0...1], minutes: prayer0.time[3...4], soundId: prayerCommingFromAdan.ringToneId, notificationBody: "It is al fajr adan")
}
What should i do in DidreceivedLocalNotification in AppDelegate?
You can use this code for scheduling your daily notifications according to time.
func ScheduleMorning() {
let calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
var dateFire=NSDate()
var fireComponents = calendar.components([.Hour, .Minute, .Second], fromDate: dateFire)
if (fireComponents.hour >= 7) {
dateFire=dateFire.dateByAddingTimeInterval(86400) // Use tomorrow's date
fireComponents = calendar.components([.Hour, .Minute, .Second], fromDate: dateFire)
}
fireComponents.hour = 7
fireComponents.minute = 0
fireComponents.second = 0
// Here is the fire time you can change as per your requirement
dateFire = calendar.dateFromComponents(fireComponents)!
let localNotification = UILocalNotification()
localNotification.fireDate = dateFire // Pass your Date here
localNotification.alertBody = "Your Message here."
localNotification.userInfo = ["CustomField1": "w00t"]
localNotification.repeatInterval = NSCalendarUnit.Day
UIApplication.sharedApplication().scheduleLocalNotification(localNotification) }
for receiving
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
// Do something serious in a real app.
print("Received Local Notification:")
print(notification.userInfo)
}
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.