Send Local Notification when Timer reaches zero SwiftUI - ios

Learning SwiftUI. I have an app that counts down timers from 30 min. As the timer doesn't work when the app is in the background, I have used user notification to get the current time app goes into the background and the time it comes to the foreground and subtracts the difference between those two times from the countdown timer I have going on so it reflects the time that has passed. Everything works fine. However, I need to be able to send a notification when the timer reaches zero.
As the timer is suspended every time the app goes into the background and the difference between how much time has passed is only calculated when the app comes into the foreground, I'm not able to find a way to send a notification when the timer reaches zero ( as the difference is only calculated when the app is in the foreground ) which negates the whole point of sending notification to let the user know the timer has ended.
Is there a way to figure out how to send a notification when the timer has reached zero without the app coming into the foreground? or any way to keep the timer running in the background so I can check if the timer has reached zero to send a notification?
Snippet of the code:
HStack {
// some code
}
.onReceive(NotificationCenter.default.publisher(
for: UIScene.didEnterBackgroundNotification)) { _ in
if isTimerStarted {
movingToBackground()
}
}
.onReceive(NotificationCenter.default.publisher(
for: UIScene.didActivateNotification)) { _ in
if isTimerStarted {
movingToForeground()
}
}
// the functions created:
func movingToBackground() {
print("Moving to the background")
notificationDate = Date()
fbManager.pause()
}
func movingToForeground() {
print("Moving to the foreground")
let deltaTime: Int = Int(Date().timeIntervalSince(notificationDate))
let deltaTimefill : Double = Double(deltaTime) / Double(300)
fbManager.breakElapsed -= deltaTime
if fbManager.breakElapsed <= 0 {
notify.sendNotification(
date: Date(),
type: "time",
timeInterval: 5,
title: "AppName+",
body: "Your timer has ended")
}
fbManager.breakFill += deltaTimefill
fbManager.startBreak()
}
Let me know if you need more code.

You can queue up the notification at any time, with the date parameter set to when you want it to be displayed.

Related

how to create countdowns in swift that will start after a countdown has finished

[New to Swift]
I am making an app on Swift 5 that displays time left before my next task is to start.
My app:
Task 1: Start at 9.30 am
Task 2: Start at 10.15 am
Let's say Current Time: 09.00 am
Countdown: Time before next task start = 00:30:02 --> 00:30:01 ...
I would like to display countdown of the nearest task.
Currently, I can only have one countdown timer on my viewonLoad() that picks up the current countdown. It continues and once it finishes it does not start next timer after it has finished. I understand I have to deal with Background state at a later date, but since I am slowly starting. My idea is to make sure I can initiate next countdown timer once my current one has expired.
So at any point when I open my app, it will always display countdown till my next task.
var count = 30 // calculation simplified ... this data is calc from two different date obj
override func viewDidLoad() {
super.viewDidLoad()
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(update), userInfo: nil, repeats: true)
}
#objc func update() {
if(count > 0) {
count = count - 1
countdownLabel.text = String(count)
}else{
timer!.invalidate()
getNextTaskTimes()
}
}
func getNextTaskTimes(){
count = 5 // not working
}
How can I achieve this please? Also, is there a better way of doing this? Given that, my user may choose to close the app and come back at a later time, I still would want my countdown to continue in the background.
Any help is appreciated. :D Thanks
I realized the above code works. I just need to make sure I do not invalidate the timer in the else block. Invalidating the timer just kills it.

How to update a label every time minute changes?

So I've been searching Stackoverflow for a good solution on how to update label if the time change, but so far the results have been unsatisfactory. Most use a timer, and that's not what I want.
What I want is if the status bar time is 8:52 PM and if some arbitrary bus leaves at 9:00 PM then I want the label to show 8 min. Then, if the time changes to 8:53 PM I want the label to show 7 min.
I'd prefer some sort of notification or delegation method rather than setting a timer.
If anyone has any suggestions on what I should do (or any 3rd party libraries that could notify if the time changes) that would be great!
You only have limited choices. If your app is not running in the foreground, you're limited to local notifications, which display an alert to the user. Using those once a minute would be awful for the user.
When your app comes to the foreground you can start a timer that is timed to fire every minute on the minute and use that to update a label in your app:
weak var timer: Timer?
func startTimer() {
timer?.invalidate()
let interval = Double(Date().timeIntervalSinceReferenceDate)
let delay = 60 - fmod(interval, 60.0)
message.text = "Delay = \(delay)"
//Create a "one-off" timer that fires on the next even minute
let _ = Timer.scheduledTimer(withTimeInterval: delay, repeats: false ) { timer in
self.message.text = "\(Date())"
self.timer = Timer.scheduledTimer(withTimeInterval: 60.0,
repeats: true ) { timer in
//Put your repeating code here.
self.message.text = "\(Date())"
}
}
}

Timer not working on real iPhone

I'm trying to use local notification but something is not working.
I have a class notification that handle all the code related to the notifications.
It's apparently working. What is not working is the way I try to trigger my notification.
When the user clicks on the home button, I call my notification class that starts a NSTimer. It repeats every second, and each 10 seconds I call a webservice.
Everything works great on my simulator, but it doesn't work on my real iPhone.
Here the code:
//as a class variable
let notif = Notification()
func applicationDidEnterBackground(application: UIApplication) {
notif.triggerTimer()
}
The notification class
class Notification: NSObject, WsOrderStatusProtocol, WsPinRequestProtocol {
var timer = NSTimer()
var time = 0
var sendNotification:Bool = true
var wsos = WsOrderStatus()
var wsoc = PinRequest()
override init() {
super.init()
self.wsos.delegate = self
self.wsoc.delegate = self
}
func triggerTimer() {
print("log INFO : class Notification, methode : triggerTimer")
NSNotificationCenter.defaultCenter().addObserver(self, selector:"orderCoupon:", name: "actionOrderCouponPressed", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector:"cancelTimer:", name: "actionCancelTimerPressed", object: nil)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: Selector("launchNotification"), userInfo: nil, repeats: true)
}
func launchNotification() {
print("log INFO : class Notification, methode : launchNotification")
time += 1
print("time \(time)")
if time % 10 == 0 {
print("modulo 10")
wsos.getOrderStatus()
}
}
}
In the simulator, I see the logs et the logs that counts to 10 etc, but with my real iphone, I only see the first log "print("log INFO : class Notification, methode : triggerTimer")" then nothing...
Do you know why ?
As Paul says in his comment, your app only spends a very brief time in the background before being suspended. Suspended means that your code doesn't run at all any more, so timers stop.
The simulator doesn't always follow the same rules. When its behavior is different than that of a device then ignore it. It lies.
If you want to have more time to do work in the background, you can ask for it using the method beginBackgroundTaskWithExpirationHandler. Make that call in your applicationDidEnterBackground method.
From testing I've found that that gives you 3 minutes of extra time. After that your expiration handler block gets executed and then you get suspended.
Apple does not want your app running indefinitely from the background. It drains the battery.
I've found that it is possible to lie and tell the system that you are an app that plays sounds from the background, and write your expiration handler to play a short "sound of silence" and then ask for another background task using beginBackgroundTaskWithExpirationHandler. However, doing that will get you rejected from the app store.

NSTimer stops when view controller is not the selected tab or not showing

I have a strange problem with my countdown timer. It fires off normally when my start button is hit, and is reinstantiated correctly when I close the app and relaunch it again. However, when I select a different tab and stay there for a while, it stops counting down, then resumes counting down from where it left off when I show the countdown tab again.
For example, if the timer is now at 00:28:00 (format is HH:MM:SS), select some other tab, stay there for 5 minutes, and then go back to the timer tab, it's only at the 27:52 mark. When I close the app (double tap the home button, swipe up my app) and reopen it, it starts off at a more reasonable 22:50 mark.
I've posted the relevant code from the class to show how I'm setting up the timer, but a summary of what it does:
I have plus (+) and minus (-) buttons somewhere that, when tapped, call recalculate().
recalculate() fires off a CalculateOperation.
A CalculateOperation computes for the starting HH:MM:ss based on the addition/removal of a new record. The successBlock of a CalculateOperation executes in the main thread.
A CalculateOperation creates the NSTimer in the successBlock if the countdownTimer hasn't been created yet.
The NSTimer executes decayCalculation() every 1 second. It reduces the calculation.timer by 1 second by calling tick().
Code:
class CalculatorViewController: MQLoadableViewController {
let calculationQueue: NSOperationQueue // Initialized in init()
var calculation: Calculation?
var countdownTimer: NSTimer?
func recalculate() {
if let profile = AppState.sharedState.currentProfile {
// Cancel all calculation operations.
self.calculationQueue.cancelAllOperations()
let calculateOperation = self.createCalculateOperation(profile)
self.calculationQueue.addOperation(calculateOperation)
}
}
func decayCalculation() {
if let calculation = self.calculation {
// tick() subtracts 1 second from the timer and adjusts the
// hours and minutes accordingly. Returns true when the timer
// goes down to 00:00:00.
let timerFinished = calculation.timer.tick()
// Pass the calculation object to update the timer label
// and other things.
if let mainView = self.primaryView as? CalculatorView {
mainView.calculation = calculation
}
// Invalidate the timer when it hits 00:00:00.
if timerFinished == true {
if let countdownTimer = self.countdownTimer {
countdownTimer.invalidate()
}
}
}
}
func createCalculateOperation(profile: Profile) -> CalculateOperation {
let calculateOperation = CalculateOperation(profile: profile)
calculateOperation.successBlock = {[unowned self] result in
if let calculation = result as? Calculation {
self.calculation = calculation
/* Hide the loading screen, show the calculation results, etc. */
// Create the NSTimer.
if self.countdownTimer == nil {
self.countdownTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("decayCalculation"), userInfo: nil, repeats: true)
}
}
}
return calculateOperation
}
}
Well, if I leave the app in some other tab and not touch the phone for a while, it eventually goes to sleep, the app resigns active, and enters the background, which stops the timer.
The solution was to set my view controller as a listener to the UIApplicationWillEnterForegroundNotification and call recalculate to correct my timer's countdown value.

Preventing every push notification being executed iOS titanium

If I get multiple push notifications whilst app is in foreground. The callback method will execute every push notification one by one.
callback : function(e) {
if (e.inBackground == 1) {
//came from background - do something.
} else {
// Titanium.UI.iPhone.setAppBadge(null);
//check type, if it is chat.
if (type == 'chat') {
//check if window is already opened or not, if so fire event handler
if (currentWindow == '_chatWindow') {
//update view directly after entering app from the background. Fire event handler
Ti.App.fireEvent('_updateChat', {});
} else if (currentWindow == '_messages') {
//refresh messages screen if on messages screen and chat message arrives
//update view directly after entering app from the background. Fire event handler
Ti.App.fireEvent('_updateMessages', {});
} else {
//display local notification
}
}
If the push notification has come from the background it is easy to deal with, as the push notification that is activated is the one the user chooses to swipe. However, if multiple push notifications come into the foreground and say it's chat, it will execute them multiple times.
How can I handle push notifications in the foreground better? Thanks
Update:
tried this code without much luck
Ti.App.addEventListener('_displayNotification', function(e) {
//store all push notifications in array
var pushArray = [];
var countPushNotifications;
//currentTime to cross reference
var currentTime = new Date();
if (currentTime - Alloy.Globals.pushTime < 3000) {
//do something
pushArray.add(e.PushNotificationData);
} else {
//after 3 seconds remove event handler
//fire event to filter array and process notification, reset time for next event
Alloy.Globals.pushTime = null;
Ti.App.removeEventListener('_displayNotification', {});
}
//first push notification, will be the current time
if(Alloy.Globals.pushTime==null){
Alloy.Globals.pushTime = currentTime;
}
});
Trying to get all the push notifications inside the array, for further filtering.
Update 2:
if (Alloy.Globals.countPushNotificationsFlag == 1) {
Alloy.Globals.countPushNotificationsFlag = null;
setTimeout(function() {
Ti.App.fireEvent('_displayNotification', {
PushMessage : message
});
}, 6000);
} else {
Alloy.Globals.countPushNotificationsFlag = 1;
Ti.App.fireEvent('_displayNotification', {
PushMessage : message
});
}
I have tried to execute push notifications alternatively.
1st notification - fires instantly.
2nd notification - fires after 6 seconds.
3rd notification - instantly.
4th notification - fires after 6 seconds.
and so on...
however the code only works for
notification 1 and 2.
Fails when it hits the 3rd notification.
you can check if Push is received in foreground or background using inBackground Property of push here is the documentation
Hope it helps.
I haven't got the Titanium experience to give you actual code, but this is the approach you need to take:
When you receive a notification, check to see if a (global) boolean receivedNotification is false
If it is false, set it to true, store the event into a global and schedule a setTimeout function for, say, 3 seconds, to process the event and reset receivedNotification to false
If receivedNotification is true, update the global event to the newer notification
In the process event method that is triggered via the timer your will do what you currently do in the first section of code.
This will ensure that the event is processed no longer than 3 seconds after it is received and that events will be processed, at most, once every three seconds.
Your code looks pretty close, except you are trying to fire the first event immediately and then subsequent events after a delay. Unfortunately I don't believe that this is possible, because you have no way of seeing if there are events queued immediately. I think that you are always going to have to incur the delay, but you can tune the delay to find a balance between responsiveness and reduced API calls -
if (Alloy.Globals.countPushNotificationsFlag == null) {
Alloy.Globals.countPushNotificationsFlag = 1;
Alloy.Globals.messageToPush=message;
setTimeout(function() {
Alloy.Globals.countPushNotificationsFlag = null;
Ti.App.fireEvent('_displayNotification', {
PushMessage : Alloy.Globals.messageToPush
});
}, 3000);
}
else {
Alloy.Globals.messageToPush=message;
}

Resources