Once my button is pressed I would like to disable my button for 24 hours and displaying a countdown on a label display a countdown until the button will be active again.
I have saved the waiting date and compared it to current date but I am not sure how to display the countdown of how much time is left in hours, minutes, and seconds.
let todaysDate = Date()
func set24HrTimer() {
let currentDate = Date()
let newDate = Date(timeInterval: 86400, since: currentDate as Date)
UserDefaults.standard.set(newDate, forKey: "waitingDate")
print("24 hours started")
//disable the button
}
if let waitingDate:Date = UserDefaults.standard.value(forKey: "waitingDate") as? Date {
if (todaysDate.compare(waitingDate as Date) == ComparisonResult.orderedDescending) {
print("show button")
}
else {
print("hide button")
}
}
You can add following code to create time countdown.
First add two variable ass follow:
fileprivate var timeWorking : Bool = false // To check is timer already scheduled
var timer:Timer? // Instance of timer
Then add following code which will calculate remaining hour, minute and second.
func timeLeftExtended(date:Date) ->NSAttributedString{
let cal = Calendar.current
let now = Date()
let calendarUnits:NSCalendar.Unit = [NSCalendar.Unit.hour, NSCalendar.Unit.minute, NSCalendar.Unit.second]
let components = (cal as NSCalendar).components(calendarUnits, from: now, to: date, options: [])
let fullCountDownStr = "\(components.hour!.description)h " + "\(components.minute!.description)m " + "\(components.second!.description)s "
let mutableStr = NSMutableAttributedString(string: fullCountDownStr, attributes: [NSAttributedString.Key.foregroundColor:UIColor.white])
for (index, char) in mutableStr.string.enumerated()
{
if(char == "h" || char == "m" || char == "s")
{
mutableStr.removeAttribute(NSAttributedString.Key.foregroundColor, range: NSMakeRange(index, 1))
mutableStr.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.lightGray], range: NSMakeRange(index, 1))
}
}
return mutableStr
}
Next, add code which is scheduledTimer if not scheduled.
func setupTimer()
{
if(!timeWorking)
{
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateCountDown), userInfo: nil, repeats: true)
self.timeWorking = true
}
}
Add code to display count down in label.
#objc func updateCountDown()
{
if let waitingDate = UserDefaults.standard.value(forKey: "waitingDate") as? Date {
self.labelCountDown.attributedText = self.timeLeftExtended(date: waitingDate)
} else {
let newDate = Calendar.current.date(byAdding: .hour, value: 24, to: Date())
UserDefaults.standard.set(newDate, forKey: "waitingDate")
self.labelCountDown.attributedText = self.timeLeftExtended(date: newDate!)
}
}
Call setupTimer() method to continue timer.
Output:
Related
I make my iOS app islamic prayer, so I need to View countdown time next prayer.
this is code from Adhan project:
so I have 5 prayer for every day, and I need countdown time between every prayer.
func formattedPrayerTime(prayer: Prayer, times: PrayerTimes?) -> some View {
guard let time = times?.time(for: prayer) else {
return Text("-")
}
return Text("\(time, formatter: dateFormatter)")
}
func formattedPrayerName(prayer: Prayer) -> some View {
switch prayer {
case .fajr:
return Text("Fajr")
case .sunrise:
return Text("Sunrise")
case .dhuhr:
return Text("Dhuhr")
case .asr:
return Text("Asr")
case .maghrib:
return Text("Maghrib")
case .isha:
return Text("Isha")
}
}
}
I use something like this before
in viewdidload in releasedate prayer time and add target for #objc func
update time func will automatically will updated if time changed every second ( timeInterval ) in scheduledTimer
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let releaseDateString = "7:07:43" // <- prayer time
let releaseDateFormatter = DateFormatter()
releaseDateFormatter.dateFormat = "HH:mm:ss"
releaseDate = releaseDateFormatter.date(from: releaseDateString)! as NSDate
countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#objc func updateTime() {
let currentDate = Date()
let calendar = Calendar.current
let diffDateComponents = calendar.dateComponents([.hour, .minute, .second], from: currentDate, to: releaseDate! as Date)
DispatchQueue.global().sync
{
hours.text = "\(diffDateComponents.hour ?? 0)"
min.text = "\(diffDateComponents.minute ?? 0)"
sec.text = "\(diffDateComponents.second ?? 0)"
}
you can use SwiftDate also it's awesome library
You could use this function:
func timeUntilNextPrayer(_ nextPrayer: Date) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
let difference = Calendar.current.dateComponents([.hour, .minute, .second], from: Date(), to: nextPrayer)
print(difference)
if nextPrayer == Date() {
timer.invalidate()
}
}
}
Call this function by passing the next prayer time as parameter. Example:
timeUntilNextPrayer(Date().addingTimeInterval(50))
I would like to send a local notification once 24 hours has passed after a button is selected. The button is currently only enabled once 24 hours has passed and the waiting time is saved in UserDegaults. I would like to know how I will be able to send a local notification to the user automatically once the 24 hours has passed.
func getNextQuote(){
let defaults = UserDefaults.standard
defaults.integer(forKey: "savedIndexKey")
let currentIndex = defaults.integer(forKey: "savedIndexKey")
var nextIndex = currentIndex+1
nextIndex = quotes.indices.contains(nextIndex) ? nextIndex : 0
defaults.set(nextIndex, forKey: "savedIndexKey")
let savedInteger = defaults.integer(forKey: "savedIndexKey")
saved = savedInteger
quotesLabel.text = quotes[savedInteger]
self.quotesLabel.fadeIn()
set24HrTimer()
update()
}
func update(){
if let waitingDate = UserDefaults.standard.value(forKey: "waitingDate") as? Date{
if let waitingDate = UserDefaults.standard.object(forKey: "waitingDate") as? Date,
waitingDate < Date() {
self.timeLabel.text = "Please check in for the day"
self.remainingLabel.text = "Did you stay clean today?"
self.remainingLabel.font = UIFont(name: "Arial", size: 32)
self.quotesLabel.isHidden = true
addButton.isHidden = false
noButton.isHidden = false
gridButton.isHidden = false
self.view.setNeedsDisplay()
print("time is up")
}else if let waitingDate = UserDefaults.standard.value(forKey: "waitingDate") as? Date {
self.timeLabel.attributedText = self.timeLeftExtended(date: waitingDate)
addButton.isHidden = true
noButton.isHidden = true
gridButton.isHidden = true
self.quotesLabel.isHidden = false
self.remainingLabel.text = "Until next check in"
self.quotesLabel.fadeIn()
print("still running")
}else{
let newDate = Calendar.current.date(byAdding: .hour, value: 24, to: Date())
UserDefaults.standard.set(newDate, forKey: "waitingDate")
self.timeLabel.attributedText = self.timeLeftExtended(date: newDate!)
print("last option")
}
}
}
func set24HrTimer() {
let currentDate = Date()
let newDate = Date(timeInterval: 86400, since: currentDate as Date)
UserDefaults.standard.setValue(newDate, forKey: "waitingDate")
print("24 hours started")
}
func timeLeftExtended(date:Date) ->NSAttributedString{
let cal = Calendar.current
let now = Date()
let calendarUnits:NSCalendar.Unit = [NSCalendar.Unit.hour, NSCalendar.Unit.minute, NSCalendar.Unit.second]
let components = cal.dateComponents([.hour, .minute, .second], from: now, to: date)
let fullCountDownStr = "\(components.hour!)h \(components.minute!)m \(components.second!)s "
let mutableStr = NSMutableAttributedString(string: fullCountDownStr, attributes: [NSAttributedString.Key.foregroundColor:UIColor.white])
for (index, char) in mutableStr.string.enumerated()
{
if(char == "h" || char == "m" || char == "s")
{
mutableStr.removeAttribute(NSAttributedString.Key.foregroundColor, range: NSMakeRange(index, 1))
mutableStr.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.lightGray], range: NSMakeRange(index, 1))
}
}
return mutableStr
}
func setupTimer()
{
if(!timeWorking)
{
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateCountDown), userInfo: nil, repeats: true)
self.timeWorking = true
}
}
You shouldn't use a timer to trigger a local notification. This function could be added as an extension to UIViewController that will let you create a UNCalendarNotificationTrigger:
import UserNotifications
import UIKit
extension UIViewController {
func createLocalNotification(title: String, hours: Int) {
let seconds = hours * 3600
let content = UNMutableNotificationContent()
content.sound = UNNotificationSound.default
content.title = title
let nextTriggerDate = Calendar.current.date(byAdding: .second, value: seconds, to: Date())!
let comps = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: nextTriggerDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: false)
let request = UNNotificationRequest(identifier: "\(seconds)", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
}
}
And can be used within your UIViewController as follows:
let MyViewController: UIViewController {
#IBAction func buttonPressed(_ sender: Any) {
self.createLocalNotification(title: "24 hours have passed!", hours: 24)
}
}
I have a UILabel which displays my current 24 hour countdown. I would like to check if the countdown is at zero and when it is to update my UILabel. The way it is set up now if it is reached 24 hours it keeps running as negative numbers. For example -1h-55m-54s
I have tried to check if UILabel contains "-" and also if the value is less than 0 however have not got it to work.
func timeLeftExtended(date:Date) ->NSAttributedString{
let cal = Calendar.current
let now = Date()
let calendarUnits:NSCalendar.Unit = [NSCalendar.Unit.hour, NSCalendar.Unit.minute, NSCalendar.Unit.second]
let components = (cal as NSCalendar).components(calendarUnits, from: now, to: date, options: [])
let fullCountDownStr = "\(components.hour!.description)h " + "\(components.minute!.description)m " + "\(components.second!.description)s "
let mutableStr = NSMutableAttributedString(string: fullCountDownStr, attributes: [NSAttributedString.Key.foregroundColor:UIColor.white])
for (index, char) in mutableStr.string.enumerated()
{
if(char == "h" || char == "m" || char == "s")
{
mutableStr.removeAttribute(NSAttributedString.Key.foregroundColor, range: NSMakeRange(index, 1))
mutableStr.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.lightGray], range: NSMakeRange(index, 1))
}
}
return mutableStr
}
func updateCountDown() {
if let waitingDate = UserDefaults.standard.value(forKey: "waitingDate") as? Date {
if let waitingDate = UserDefaults.standard.object(forKey: "waitingDate") as? Date,
waitingDate < Date() {
self.timeLabel.text = "Time ran out"
print("It is time for you to check in")
}
// if self.timeLeftExtended(date: waitingDate) <= 0{
//change label
}
self.timeLabel.attributedText = self.timeLeftExtended(date: waitingDate)
} else {
let newDate = Calendar.current.date(byAdding: .hour, value: 24, to: Date())
UserDefaults.standard.set(newDate, forKey: "waitingDate")
self.timeLabel.attributedText = self.timeLeftExtended(date: newDate!)
}
}
Just compare the dates
if let waitingDate = UserDefaults.standard.object(forKey: "waitingDate") as? Date,
waitingDate < Date() {
// waitingDate exists and is earlier than the current date
}
And you can create the countdown string much shorter
let components = cal.dateComponents([.hour, .minute, .second], from: now, to: date)
let fullCountDownStr = "\(components.hour!)h \(components.minute!)m \(components.second!)s "
Here is how I use UILabel to display count down timer
First, these are my variables
#IBOutlet weak var lblCountDownTime: UILabel! //label to display count down
var countDownTimer: Timer? // timer to count down
Second, this is how I display count down, hope it help
self.countDownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.countDownTime), userInfo: nil, repeats: true);
func countDownTime() {
let now = Date();
let calendar = Calendar.current;
let comps = calendar.dateComponents(Set<Calendar.Component>([.minute, .second]), from: now, to: self.startTime);
var strMinute = "\(comps.minute!)";
var strSecond = "\(comps.second!)";
if (comps.minute! < 10) {
strMinute = "0\(comps.minute!)";
}
if (comps.second! < 10) {
strSecond = "0\(comps.second!)";
}
if (comps.minute! <= 0 && comps.second! <= 0) {
self.countDownTimer?.invalidate();
}
else {
self.lblCountDownTime.text = "\(strMinute):\(strSecond)\"";
}
}
i am working on an alarm app in which user can select days of week on which he wants to ring the alarm. In my code if I'm executing the condition for "same day" only then alarm is ringing on every day. But when I'm selecting the other two conditions i.e. "Next day" and "Previous day", notifications are not firing. Although the notifications are created but they are not firing. Any help would be appreciated. Here's my code
func scheduleUserNotifications(for alarm: Alarm)
{
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "HelloK"
if alarm.name.isEmpty == false
{
notificationContent.body = "Please respond to your alarm \(alarm.name)"
}
else
{
alarm.name = "Empty"
}
let song = AlarmController.shared.tone
print (song)
notificationContent.sound = UNNotificationSound(named: song)
print(notificationContent.sound)
print(AlarmController.shared.counter)
var countNum: Int!
if AlarmController.shared.counter == "30 Minutes"
{
countNum = 6
}
else if AlarmController.shared.counter == "1 Hour"
{
countNum = 12
}
else
{
countNum = 36
}
print(countNum)
var daysValue: [Int]
daysValue = AlarmController.shared.days
print(daysValue)
var dateVal = 0
for day in daysValue
{
print(dateVal)
if day == 1
{
print("hello")
guard let fireDate = alarm.fireDate else { return }
print(fireDate)
let dateToChange = fireDate
var nextDate = dateToChange
var difference : Int?
// var addComponent : Calendar.Component?
let weekday = Calendar.current.component(.weekday, from: fireDate)
print(weekday)
if dateVal == weekday-1
{
print("same day")
nextDate = fireDate
// nextDate = fireDate.addingTimeInterval(7*86400)
print(nextDate)
}
else if dateVal > weekday-1
{
print("next day")
difference = dateVal - (weekday-1)
print(difference!)
nextDate = fireDate.addingTimeInterval(TimeInterval(difference!*86400))
print(nextDate)
}
else if dateVal < weekday-1
{
print("previous day")
difference = 7-(weekday-1)+dateVal
print(difference!)
nextDate = fireDate.addingTimeInterval(TimeInterval(difference!*86400))
print(nextDate)
}
for ind in 0...countNum
{
let date = nextDate.addingTimeInterval((1.0*Double(ind)) * 60.0)
print(date)
let triggerDate = Calendar.current.dateComponents([.weekday, .hour, .minute, .second], from: date)
DispatchQueue.main.async
{
print(triggerDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDate, repeats: true)
let request = UNNotificationRequest(identifier: "\(alarm.uuid)\(ind)", content: notificationContent, trigger: trigger)
// print(request)
UNUserNotificationCenter.current().add(request)
{
(error) in
if let error = error
{
print("Unable to add notification request, \(error.localizedDescription)")
}
}
}
}
}
dateVal += 1
}
let center = UNUserNotificationCenter.current()
center.getPendingNotificationRequests { (notifications) in
print("Count: \(notifications.count)")
for item in notifications {
print(item.content)
}
}
}
when I remove this part of my code, notifications are working fine but they are firing on every day
else if dateVal > weekday-1
{
print("next day")
difference = dateVal - (weekday-1)
print(difference!)
nextDate = fireDate.addingTimeInterval(TimeInterval(difference!*86400))
print(nextDate)
}
else if dateVal < weekday-1
{
print("previous day")
difference = 7-(weekday-1)+dateVal
print(difference!)
nextDate = fireDate.addingTimeInterval(TimeInterval(difference!*86400))
print(nextDate)
}
but when i uncomment it, no notification appears
I have a list of quotes.
I want to have a label that changes every 24H to a new Quote.
How can I do this? Like how can I manage a day?
I know I could just use an API but I want to use my own quotes and I am not able to create an API on my own yet.
Supposing you have an array of quotes:
let numberOfQuotes = 3
let quotes = ["quote a", "quote b", "quote c"]
override func viewDidLoad() {
_ = Timer.scheduledTimer(timeInterval: TimeInterval(60),
target: self,
selector: #selector(self.updateQuote),
userInfo: nil,
repeats: true)
}
func updateQuote() {
let lastUpdate = UserDefaults.standard.object(forKey: "lastUpdate") as? Date
if lastUpdate != nil {
let date1:Date = Date() // Same you did before with timeNow variable
let date2: Date = Date(timeIntervalSince1970: lastUpdate)
let calender:Calendar = Calendar.current
let components: DateComponents = calender.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date1, to: date2)
// you can use components month, hour, second.... to update your message, in your case, we will day
if components.day! >= 1 {
UserDefaults.standard.set(Date(), forKey: "lastUpdate")
yourLabel.text = quotes[randomInt(0,numberOfQuotes)]
}
} else { //firstTime running
UserDefaults.standard.set(Date(), forKey: "lastUpdate")
yourLabel.text = quotes[randomInt(0,numberOfQuotes)]
}
}
func randomInt(min: Int, max:Int) -> Int {
return min + Int(arc4random_uniform(UInt32((max - 1) - min + 1)))
}
This code is as is, by your description, it does exactly what you want.