What would be a possible way to update labels that displays static time received once from API.I have a tableView where each cell displays city name current temperature and time just like the native iPhone WeatherApp.Is there a way to observe the device clock minutes so I can trigger some code to update the times when a minute goes by?
You can subclass a UILabel and add a timer to it so that it autoupdates itself:
Considering your last question where you get the timeZone offset from GMT from your API, you can subclass a UILabel, add a timeZone property to it and a didSet closure to setup a timer to fire at the next even minute with a 60 seconds interval and set it to repeat. Add a selector to update its label every time this method is called:
class Clock: UILabel {
let dateFormatter = DateFormatter()
var timer = Timer()
var timeZone: Int = 0 {
didSet {
dateFormatter.timeStyle = .short
dateFormatter.timeZone = TimeZone(secondsFromGMT: timeZone)
var components = Date().components
components.minute! += 1
components.second = 0
components.nanosecond = 0
timer = .init(fireAt: components.date!, interval: 60, target: self, selector: #selector(update), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .common)
update()
}
}
#objc func update(_ timer: Timer? = nil) {
text = dateFormatter.string(from: Date())
}
}
extension Date {
var components: DateComponents {
Calendar.current.dateComponents(in: .current, from: self)
}
}
Now you can just create your clock label and when you setup its timeZone property it will start running automatically:
let clock: Clock = .init(frame: .init(origin: .zero,
size: .init(width: 200, height: 30)))
clock.timeZone = -14400
clock.text // "11:01 PM"
Related
I have start date and end date strings, how to show the end Sale timer like this image?
"Start-date":"Dec 18, 2019 05:15:00 +0000","End-date":"Dec 27, 2019 11:15:39 +0000"
Please help!
You should convert the date string to Date first, then you can use timer to update the timer labels based on start and end date.
Use Calendar and dateComponents method to find the day, hour, minute and second differences between dates and then set the value on day, hour, minute and second labels as like you attached image.
Example implementation:
var timer:Timer?
var endDate:Date?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
startTimer()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopTimer()
}
func startTimer(){
let endDateStr = "Dec 27, 2019 11:15:39 +0000"
let dateFormat = "MMM d, yyyy HH:mm:ss Z"
let dateFormater = DateFormatter()
dateFormater.dateFormat = dateFormat
endDate = dateFormater.date(from: endDateStr)
//stop timer if it's already running
stopTimer()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateSaleTime), userInfo: nil, repeats: true)
}
func stopTimer(){
if timer != nil{
timer!.invalidate()
timer = nil
}
}
func updateSaleTime(){
guard let d2 = endDate else {
stopTimer()//Check if the date-format is correct for end date string.
return
}
let cal = Calendar.current
let components = cal.dateComponents([.day, .hour, .minute, .second], from: Date(), to: d2)
let day = components.day!
let hour = components.hour!
let minute = components.minute!
let second = components.second!
//set the value on day, hour, minute and second labels as like you attached image.
}
If you calculate the difference between the startDate and the endDate it will alway be the same . Instead you can calculate the difference to endDate from the current date.
Check the following implementation.
class ViewController: UIViewController {
let endDate : Date? = {
// To create Date from date string received from server
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd, yyyy HH:mm:ss Z"
// Convert to desired Timezone
return dateFormatter.date(from: "Dec 18, 2019 06:30:39 +0000")
}()
var timer : Timer?
private func starCountDown() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
private func stopCountDown() {
timer?.invalidate()
}
#objc func updateTime() {
guard let endDate = endDate else {
stopCountDown()
return
}
let countdown = Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date(), to: endDate)
let days = countdown.day!
let hours = countdown.hour!
let minutes = countdown.minute!
let seconds = countdown.second!
if days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0 {
stopCountDown()
print("Offer Expired")
return
}
print(String(format: "%02d Days , %02d Hours, %02d Mins, %02d Sec", days, hours, minutes, seconds))
// set values as per your requirement
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillAppear(animated)
stopCountDown()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
starCountDown()
}
}
My time label is displaying the time, when I open my app but it won't update it live. I had a look at other answers but they didn't make sense.
// CURRENT TIME
#IBOutlet weak var currentTimeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
getCurrentTime()
}
// FORMAT TIME
func getCurrentTime(){
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm"
let str = formatter.string(from: Date())
currentTimeLabel.text = str
}
I want my app to update the time label live. Thanks in advance. It's probably a really simple fix.
Use Timer for your requirement,
class ViewController: UIViewController {
#IBOutlet weak var currentTimeLabel: UILabel!
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
getCurrentTime()
}
private func getCurrentTime() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector:#selector(self.currentTime) , userInfo: nil, repeats: true)
}
#objc func currentTime() {
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm"
currentTimeLabel.text = formatter.string(from: Date())
}
}
You can create Timer with repeating every minute (because you don't need seconds for anything) starting in the next minute (so call getCurrentTime() once before you start Timer).
Every minute code inside timer's closure gets executed so you can say that you want to call getCurrentTime(). Now your currentTimeLabel will be updated every minute
let now = Date()
let date = Calendar.current.date(bySettingHour: Calendar.current.component(.hour, from: now), minute: Calendar.current.component(.minute, from: now) + 1, second: 0, of: now)!
let timer = Timer(fire: date, timeInterval: 60, repeats: true) { _ in
self.getCurrentTime()
}
Also I would recommend you to have formatter variable outside of the method (in global scope)
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "hh:mm" // or "hh:mm a" if you need to have am or pm symbols
return formatter
}()
and then in getCurrentTime() just get String and change text of currentTimeLabel
func getCurrentTime() {
currentTimeLabel.text = formatter.string(from: Date())
}
I have been struggling making a countdown in Swift where it shows only the days left until some date where the input is the DatePicker... I have creo experience with Swift so, I have been struggling for a while. I tried some similar answers here but didn't work, I watched a tutorial but is a normal countdown with months, days, minutes and seconds, this is the code.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var timeLabel: UILabel!
let formatter = DateFormatter()
let userCleander = Calendar.current;
let requestedComponent : Set<Calendar.Component> = [
Calendar.Component.month,
Calendar.Component.day
]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timePrinter), userInfo: nil, repeats: true)
timer.fire()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func timeCalculator(dateFormat: String, endTime: String, startTime: Date = Date()) -> DateComponents {
formatter.dateFormat = dateFormat
let _startTime = startTime
let _endTime = formatter.date(from: endTime)
let timeDifference = userCleander.dateComponents(requestedComponent, from: _startTime, to: _endTime!)
return timeDifference
}
func timePrinter() -> Void {
let time = timeCalculator(dateFormat: "MM/dd/yyyy a", endTime: "12/25/2018 a")
timeLabel.text = "\(time.month!) Months \(time.day!) Days"
}
}
Several things: Don't use strings to compare dates. Use Date objects and Calendar operations. (More on that in a second.)
Don't run a timer once a second. Save the current date to user defaults. When your app is launched, compare the saved date to the current date and see if the day has changed.
When running, listen for UIApplicationSignificantTimeChange notifications, and when you get one, check to see if the date has changed.
As for comparing the current date to the user-selected date, you've got the right idea using dateComponents(_:from:to:), but you should pass in components of just [.day].
EDIT:
Code like this would do the trick:
override func viewDidLoad() {
super.viewDidLoad()
//Set up the date picker to pick dates, not dates & times
datePicker.datePickerMode = .date
//Force the date picker to use midnight today as it's base date and
//to pick a date at least 1 day in the future
guard let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date()),
let tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: today)
else {
return
}
datePicker.minimumDate = tomorrow
datePicker.date = tomorrow
}
#IBAction func datePickerChanged(_ sender: UIDatePicker) {
let future = datePicker.date
//Use midnight today as the starting date
guard let today = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: Date()) else { return }
//Calculate the number of days between today and the user's chosen day.
let difference = Calendar.current.dateComponents([.day], from: today, to: future)
guard let days = difference.day else { return }
let ess = days > 1 ? "s" : ""
infoLabel.text = "That date is \(days) day\(ess) away."
}
I have a UIDatePicker which is restricted to time only. The app saves the selected time from the datePicker in a variable and when this time is reached it fires a UILocalNotification.
So there is a label which shows the remaining time until the Local Notification fires. So it basically works a bit like a countdown Timer.
How can I achieve this?
Date to String: (for displaying the fireDate)
func formatTimeForDisplay(date:NSDate) -> String {
let formatter = NSDateFormatter()
formatter.locale = NSLocale.currentLocale()
formatter.timeStyle = NSDateFormatterStyle.ShortStyle
return formatter.stringFromDate(date)
}
Extension for NSLocalNotification: (for converting the NSDate from UIDatePicker to fireDate for the LocalNotification)
extension NSDate {
var minute: Int {
return NSCalendar.currentCalendar().component(.Minute, fromDate: self)
}
var hour: Int {
return NSCalendar.currentCalendar().component(.Hour, fromDate: self)
}
var day: Int {
return NSCalendar.currentCalendar().component(.Day, fromDate: self)
}
var month: Int {
return NSCalendar.currentCalendar().component(.Month, fromDate: self)
}
var year: Int {
return NSCalendar.currentCalendar().component(.Year, fromDate: self)
}
var fireDate: NSDate {
let today = NSDate()
return NSCalendar.currentCalendar().dateWithEra(1,
year: today.year,
month: today.month,
day: { hour > today.hour || (hour == today.hour
&& minute > today.minute) ? today.day : today.day+1 }(),
hour: hour,
minute: minute,
second: 0,
nanosecond: 0
)!
}
To do this, you'll need to use an NSTimer to get update the count down label.
In your ViewController, create a variable for the timer.
var timer = NSTimer()
I also set up a temp variable to hold the future date - you didn't post code, so I'm not sure what yours is called. In my code, I refer to it as futureDate
let futureDate = NSDate().dateByAddingTimeInterval(60*60*2.4)
When your use selects a date and you want the countdown to begin, you'll want to initiate the timer firing to call a method that will update the label. In thie example, i call it updateTimeLeft.
timer = NSTimer.scheduledTimerWithTimeInterval(0.5, target: self, selector: "updateTimeLeft", userInfo: nil, repeats: true)
Then, we simply need to calculate the difference between now and the future date when the local notification will fire. We do that in the updateTimeLeft method. In my code, I have an IBOutlet to the UILabel called timeLeft.
func updateTimeLeft()
{
let elapsedTime = futureDate.timeIntervalSinceDate(NSDate())
self.timeLeft.text = NSDateComponentsFormatter().stringFromTimeInterval(elapsedTime)
}
To display user friendly string you may refer to NSDateComponentsFormatter if your app is targeted for iOS 8 and higher.
Also, you may encounter one problem. UIDatePicker gives back a NSDate object, which encapsulates amount of seconds from Jan 1, 2001. Even if you see only time component in date picker.
This means, you should pay attention to the date value of UIDatePicker and maybe "fix" it with NSCalendar API.
This is a regular timer (chrono) with minutes and seconds.
It works fine but is there a way to have the same result with an NSDateFormatter.
var min = 0
var sec = 0
func stringFromTimeInterval(interval: NSTimeInterval) -> String {
let interval = Int(interval)
let seconds = interval % 60
let minutes = (interval / 60)
return String(format: "%2d:%02d",minutes, seconds)
}
func updateTimer(){
sec++
self.timeElapsed.text = self.stringFromTimeInterval(Double(sec))
}
Here is my attempt :
var sec = 0
func theTime(interval:NSTimeInterval) -> String {
var dateFormatter = NSDateFormatter()
var date = NSDate(timeIntervalSince1970: NSTimeInterval())
dateFormatter.dateFormat = "mm:ss"
return dateFormatter.stringFromDate(date)
}
func updateTheTime(){
sec++
self.newTimer.text = self.theTime(Double(sec))
}
I'm doing something wrong because it is always 00:00.
How can I make it work?
You can use NSDate and NSDateFormatter to get time string.
create an object of NSDateFormatter and NSDate, then set the style of your time to NSDateFormmatter, then call stringFromDate to get time string.
var dateFormatter = NSDateFormatter()
var date = NSDate(timeIntervalSince1970: interval)
dateFormatter.dateFormat = "mm-ss"
dateFormatter.stringFromDate(date)
You can use NSDateComponentsFormatter.
class ViewController: UIViewController {
// 1
var startDate: NSDate!
// 2
#IBOutlet weak var timerLabel: UILabel!
// 3
func tick() {
// 4
let elapsed = NSDate().timeIntervalSinceDate(startDate)
// 5
let formattedTime = NSDateComponentsFormatter().stringFromTimeInterval(elapsed)
// 6
timerLabel.text = formattedTime
}
// 7
#IBAction func startButtonPressed() {
// 8
startDate = NSDate()
// 9
NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "tick", userInfo: nil, repeats: true)
}
}
Here's what's happening in that Swift code:
The startDate property will be initialized in startButtonPressed()
The timerLabel will show the output. It is a standard UILabel.
tick() will be called from an NSTimer
elapsed calculates how much time has transpired since the startDate. NSDate() is a handy way to get the current date.
This is where the magic happens. The NSDateComponentsFormatter will output a correctly trimmed String for the elapsed time.
The formattedTime is displayed on the screen
startButtonPressed() is called when the user taps the Start button.
The startDate property is set to the current date.
An NSTimer will call tick() every 1 second.
Here's a screenshot of what it looks like in practice with:
four seconds elapsed
one minute and four seconds elapsed