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.
Related
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.
self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler: {
print("animateRightToLeft: went here")
if let indentifier = self.backgroundTaskIdentifier {
print("animateRightToLeft: stop here")
UIApplication.shared.endBackgroundTask(indentifier)
}
})
My App auto killed after some time if App goes background.
Can some one advice is it because of the above code?
It would be much easier to help you if you explain what you are trying to do? The code you provided will only allow your app to execute code in background for limited amount of time (currently 180 seconds on my iPhone 7).
Detailed:
Once you call beginBackgroundTask, you are given a timer which starts running after your app goes to background. While that timer is running, your app will be executing code even in background. When this timer runs out, or you call endBackgroundTask, your code will stop executing in background. Also if that timer runs out before you called endBackgroundTask, your expiration handler will be called and you should call endBackgroundTask there.
Please note that the code you wrote in the expirationHandler will be called only if you don't call endBackgroundTask before timer runs out.
You can use this code to test how it all behaves, e.g. if you run it as is, app will print backgroundTimeRemaining in the console even when in background. If you comment beginBackgroundTask your app will not print anything after it goes to background.
private var backgroundTaskIdentifier: UIBackgroundTaskIdentifier?
var timer: Timer?
#IBAction func buttontapped(_ sender: Any)
{
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block:
{
(timer) in
NSLog("$$$$$ Time remaining: \(UIApplication.shared.backgroundTimeRemaining)")
})
self.backgroundTaskIdentifier = UIApplication.shared.beginBackgroundTask(expirationHandler:
{
NSLog("$$$$$ Timer expired: Your app will not be executing code in background anymore.")
if let indentifier = self.backgroundTaskIdentifier
{
UIApplication.shared.endBackgroundTask(indentifier)
}
})
NSLog("$$$$$ start")
DispatchQueue.main.asyncAfter(deadline:.now() + 30)
{
NSLog("$$$$$ end")
if let indentifier = self.backgroundTaskIdentifier
{
UIApplication.shared.endBackgroundTask(indentifier)
}
}
}
From Docs beginBackgroundTask(expirationHandler:)
This method requests additional background execution time for your app. Call this method when leaving a task unfinished might be detrimental to your app’s user experience. For example, call this method before writing data to a file to prevent the system from suspending your app while the operation is in progress. Do not use this method simply to keep your app running after it moves to the background.
Each call to this method must be balanced by a matching call to the endBackgroundTask(_:) method.
My App auto killed after some time if App goes background , is it because of the above code?
no it isn't the above snippet only asks for additional time until task is finished , your app will be terminated anyway
I have an iOS app that I wrote with Swift with Firebase Auth/DB for the backend. I would like to measure how long it takes for a user to complete specific actions. I'm actually not interested in the response time, but am interested in the total time it takes to complete something.
In other words, I want to measure how long it takes to login, click on a button, receive a push notification, click "Ok" on that notification, etc. I also want to log how many seconds it took to get from one thing to the next (i.e. login time: 2.5 seconds, time to push a specific button: 4 seconds, etc.).
I am trying out Firebase Analytics, and it almost works, but not quite. I can log specific events, such as login, button presses, etc., but it just logs the that the event occurs, not how long it took.
Ideally, I would record all of this data on the specific users I give to try my app, so I could look at all the data, find averages and other useful information.
consider using a a Timer, maybe something like this.
import UIKit
class Whatever: UIViewController {
var timer = Timer()
var currentTime = 0.00
func timeCounter() {
currentTime += 0.01
}
override func viewDidLoad() {
timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(Whatever.timeCounter), userInfo: nil, repeats: true)
}
#IBAction func buttonPressed(_ sender: Any) {
timer.invalidate()
//whatever button does
}
}
This way when the application begins the timer will begin, when the final button is pushed the timer will stop. You will have the value of how long it took stored as currentTime.
Hope this helps!
My application needs to have more than 10 local notifications at different time (not recurring) on daily basis. According to iOS official docs, i can only schedule 64 notifications. I have tried solutions from this and several others articles on the web but found no working solution.
Is there any way i can schedule the Local notifications at different times even if my app is not running for several days (or killed)?
There is no direct way For doing this.
If you want to do it anyhow(not proper solution, just a patch), then just go via following way.
wake up the app in background - which can be done by using starting location manager, which will wake up your app in background when location get updated, at that time you can do whatever you like with local notification or any other things.
Before applying this method - make sure that - this is too much battery consuming way + not proper way. Your app might get rejected from apple if it is using too much battery.
Read following details(copied from other question from stackoverflow):
An app can be woken by a significant location change, if the app has indicated that it wants to monitor such events.
See: [CLLocationManager Docs][1]
Look for a method called startMonitoringSignificantLocationChanges. If a significant location change occurs while your app is not in the foreground or isn't running at all, your application will be launched in the background, allowing the app to perform background-only operations (e.g. no view code will run).
When you want your app to work in background even when it is killed then you have to enable 'Background Modes' from your Project's Capabilities and fire your Local Notifications method with specific times through that.
Here is a little code snippet to get you started:
var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
override public func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.reinstateBackgroundTask), name: UIApplicationDidBecomeActiveNotification, object: nil)
}
//MARK: Background Task / Local Notifications / Checkin
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func reinstateBackgroundTask() {
if backgroundTask == UIBackgroundTaskInvalid {
registerBackgroundTask()
}
}
func registerBackgroundTask() {
backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
[unowned self] in
self.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
NSLog("Background task ended.")
UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
func dosomething() {
registerBackgroundTask()
//Fire Local Notifications accordingly…
//Use NSTimer if you want it with specific time intervals
}
Have a look at this wonderful Raywenderlich Tutorial about Background Modes in iOS.
Apple's Documentation
My app (prefix "AAS") is basically a game where users lose points every day they don't play. I use UILocalNotifications to alert the user that they've lost points, and invite them back to play. One of my view controllers displays when the points have changed, and it's pretty simple to send out an NSNotification when a UILocalNotification is fired while the app is open).
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
if notification.userInfo != nil {
if let notificationName = notification.userInfo![AASNotification.ActionKey] as? String {
NSNotificationCenter.defaultCenter().postNotificationName(notificationName, object: nil, userInfo: nil)
}
}
}
When the app is reopened after being inactive, one of the classes calculates how many points are lost. Great. Bulletproof, except when the user disallows my app to use NotificationCenter, the app will not be updated if it's open when the notification is supposed to fire. For this case, I wrote my own implementation of a timed notification queue that would mimic UILocalNotification to a certain extent while my app is open. But I thought, someone must have had this problem before, and maybe there is a cocoapod for it.
So my question to the community is, does someone know of a library that dispatches timed NSNotifications? Or a different approach to this problem? Here's my solution, which is barebones and works for the purpose I need:
https://github.com/JamesPerlman/JPScheduledNotificationCenter
I'd love to use one that was coded by a professional and is well tested and feature rich. (I was made aware that this request is off topic for SO.)
Edits:
I want to be able to queue up any amount of NSNotifications to be fired at arbitrary dates. Obviously the NSNotifications can only be received by my app while it is open, that's fine. I do not know the expense of using one NSTimer for each NSNotification (could be hundreds of NSTimers all on the run loop), so my solution only uses one NSTimer at a time. I want the ability to schedule and cancel NSNotifications just like you can do with UINotifications.
You could try NSTimer (NSTimer class reference). In your AppDelegate you can create a method similar to your didReceiveLocalNotification method to execute when the timer is triggered. Also, create an NSUserDefault to store the next time you need to trigger the timer. Finally, at the point where you want to begin the countdown, get the time interval from the current time until the time you want to trigger the event, and set the timer.
So in your AppDelegate, register the default and implement the notifyPlayer:
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
let userDefaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.registerDefaults(["alertTime": NSDate()]) //initial value
return true
}
func notifyPlayer() {
// Calculate points and notify relevant viewcontroller to alert player.
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
let lastNotificationTime = defaults.objectForKey("alertTime") as! NSDate
let nextNotificationTime = lastNotificationTime.dateByAddingTimeInterval(86400)
defaults.setObject(nextNotificationTime, forKey: "alertTime")
}
}
Now set the timer wherever it makes sense, probably in your app's initial view controller.
class InitialVewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
let savedTime = defaults.objectForKey("alertTime") as! NSDate
let countDownTime = savedTime.timeIntervalSinceNow
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
NSTimer.scheduledTimerWithTimeInterval(countDownTime,
target: appDelegate,
selector: #selector(AppDelegate.notifyPlayer()),
userInfo: nil,
repeats: false)
}
}
It's not perfect, as I haven't tested it, but I think the concept will work for you.
Edit: Just to clarify, this would solve your problem of alerting the user while he is using the app, but won't do anything when the app is not in use. I don't know of any way to send users notification center notifications when permission hasn't been granted.