I want to execute some code when iPhone battery iPhone run out of battery.
Will func applicationWillTerminate(_ application: UIApplication) { be called? If no then does exist any other way to track this? Thanks in advance.
There is no support for this instead you have to decide when the battery level is so low that you need to run your code.
What you can do is listen to the notification batteryLevelDidChangeNotification which is done by activating monitoring see isBatteryMonitoringEnabled
Like mentioned in the documentation the notifications are sent no more than once per minute so you have to determine a battery level that is low enough to be considered as almost depleted and then execute the code.
I have no idea what this could be, from personal experience I have seen my phones stay alive a long time on values under 5% but also seen them die shortly after going below 10%
You can use the following code snippet:
func startObservingBatteryDrainage(){
if UIDevice.current.isBatteryMonitoringEnabled && (UIDevice.current.batteryState == .unplugged || UIDevice.current.batteryState == .unknown){
if UIDevice.current.batteryLevel < 0.1{
NotificationCenter.default.addObserver(self, selector: #selector(funcWhenBatteryIsAlmostDrained), name: UIDevice.batteryLevelDidChangeNotification, object: nil)
}
}
}
#objc func funcWhenBatteryIsAlmostDrained(){
//do stuff
}
Related
In my application, I have a home screen. Anytime this screen is loaded, it needs to make a network request (currently using Alamofire 5.2) to fetch the latest data needed to display. I am running into this issue that I believe has to do with my implementation of view lifecycles, but I am not sure how to get around it to achieve my desired effect.
Scenario 1
override func viewDidLoad() {
NotificationCenter.default.addObserver(
self,
selector: #selector(wokeUp),
name: UIApplication.didBecomeActiveNotification,
object: nil)
}
#objc func wokeUp() {
pageLoaded()
}
func pageLoaded(){
// network requests made here
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}
Here I am registering the observer within viewDidLoad. The reason I need this is many of our users will not close the application. We've found on more than a few occasions that they will let the phone sleep while the application is open, so we need to make this request when the phone is woken up and immediately back at this screen. the didBecomeActiveNotification seems to be what takes care of that.
Scenario 2
override func viewDidAppear(_ animated: Bool) {
pageLoaded() // same network request as example above
}
We also need to call this request within viewDidAppear, as there are quite a few flows where the user is brought back to this home page from another view in the application, and it's important that the request is made here as well (what the user does in the other flows has an impact of what shows up here, so we have to make sure it's updated).
The problem is that what I am finding is these two scenario will occasionally clash - our server essentially gets the same request twice, which is not ideal and causing issues. I've noticed the majority (if not all) of the problems occur when opening the application when it's no longer in memory (viewDidLoad gets called); the case of bringing the app from the background to foreground while it's still in memory is working as expected, but I have no idea what other implementation I could take to cover all of my bases here. Any insight would be appreciated.
Why not just add a simple boolean flag to your networking logic to make sure only 1 request gets fired. e.g.
class SomeViewController {
private var isFetching = false
...
func pageLoaded() {
guard isFetching == false else {
return
}
isFetching = true
// do some networking
// ....
// inside the callback / error cases
isFetching = false
}
}
depending on how big your app is, if you have many requests and/or the same request being fired on many screens. Move all your networking to another class and have this logic inside the network service rather than the viewController
I have an iOS app which uses CoreLocation and WiFi SSID information. My app was working fine until iOS 13 arrived but since then it's giving me a lot of issues especially when app goes in background. I have been using one timer in background as background task which also not work after 30 seconds and especially app got killed in background in the same time frame. I have seen some people saying that iOS 13 has been strict for background task and timing but I still have not found any direct references or links by apple which supports this claims. Is there anyone else facing the same issues then please share your insights. Thanks
I have one background task for timer:
var bgTask: UIBackgroundTaskIdentifier?
var updateTimer: Timer?
func applicationDidEnterBackground(_ application: UIApplication) {
bgTask = application.beginBackgroundTask(withName: "MyTask", expirationHandler: {() -> Void in
if let bgTask = self.bgTask {
application.endBackgroundTask(bgTask)
self.bgTask = UIBackgroundTaskIdentifier(rawValue: convertFromUIBackgroundTaskIdentifier(UIBackgroundTaskIdentifier.invalid))
}
})
DispatchQueue.main.async(execute: {() -> Void in
self.updateTimer = Timer.scheduledTimer(timeInterval: 2.0, target: self, selector: #selector(self.checkNetworkStatus), userInfo: nil, repeats: true)
})
}
#objc func checkNetworkStatus() {
print("Timer calledddd")
}
The name background task is somewhat misappropriate.
Specifically, beginBackgroundTask(expirationHandler:) doesn’t actually start any sort of background task, but rather it tells the system that you have started some ongoing work that you want to continue even if your app is in the background.
You still have to write the code to create and manage that work.
So it’s best to think of the background task API as raising a “don’t suspend me” assertion.
iOS 13 puts strict limits on the total amount of time that you can prevent suspension using background tasks.
iOS 13 has reduced the from-the-foreground value to 30 seconds.
a. 3 minutes, when your app has moved from the foreground to the background
b. 30 seconds, when your app was resumed in the background
You can get a rough estimate of the amount of time available to you by looking at UIApplication() backgroundTimeRemaining property.
Further info here and here.
You may want to consider a different API to achieve your goals.
With the lines below it is possible to get a notification on app launch that the system clock (local device time) was changed by the user.
NotificationCenter.default.addObserver(self, selector: #selector(TimeWasChanged), name: NSNotification.Name.NSSystemClockDidChange, object: nil)
#objc func TimeWasChanged(){
print("time was changed by the user!")
}
I'm wondering if there is also a way to get notified about the amount the time value was changed (e.g. clock set +10 minutes from 8:20pm to 8:30pm).
If theres nothing like this already implemented would it be possible to calculate this offset manually (without using internet --> No NHC or JSON)?
Help would be very appreciated, Jane
Is it correct to use NSNotificationCenter as the only handler for all the events inside the app?
Is it fine if I put a list of all possible events like this:
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loginUser:", name: "userWillLogin", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "showError:", name: "userLoginError", object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "loadMainScreen:", name: "userDidLogin", object: nil)
// Is it ok if I put 10 or 20 more event listeners here?
}
Or is the intention of this functionality different? I find it appealing to use event listeners and handlers in this manner, but not sure if it's the recommended way to pass events and data across the app regarding performance and best practices.
When using only notifications you'll find yourself in troubles to debug what happened that something is broken. Notifications make tracking down bugs hard, because not always it's obvious what was the flow. There are situations when you should use them, but don't do it always via notifications - it's less readable.
The best way would be to use delegate pattern / blocks for 1:1 relationships and NSNotificationCenter / KVO for 1:n relationships.
Check out this link: http://nshipster.com/nsnotification-and-nsnotificationcenter/
I've got a problem with location services. I can't set up a function that updates my location coordinates in the background by a NSTimer. Here is my code from appDelegate:
var locationManager = CLLocationManager()
func applicationDidEnterBackground(application: UIApplication) {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
self.theTimer = NSTimer(fireDate: NSDate(), interval: 40, target: self, selector: "handleTimer", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(self.theTimer, forMode: NSDefaultRunLoopMode)
}
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
var locValue:CLLocationCoordinate2D = manager.location.coordinate
println("dinBack = \(locValue.latitude) \(locValue.longitude)")
self.locationManager.stopUpdatingLocation()
}
func handleTimer(){
println("started")
self.locationManager.startUpdatingLocation()
}
PS. - Of course that i've imported corelocation.
- When I get back into the app, the console prints what should have printed in the background.
You can not make an NSTimer work like this while your application is in the background. NSTimer's are not "real-time mechanisms". From the official documentation:
Timers work in conjunction with run loops. To use a timer effectively, you should be aware of how run loops operate—see NSRunLoop and Threading Programming Guide. Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time.
Emphasis mine.
The important take away from this is that while your application is in the background, any run loop that your timer would have been scheduled on is not actively running.
As soon as your app returns to the foreground, this run loop fires back up, sees that your timer is overdue, and sends the message to the selector.
With iOS 7 and forward, if you want to perform operations in the background, you can tell the OS that you want to perform "background fetches".
To set this up, we must first tell the OS how frequently we want to fetch data, so in didFinishLaunching..., add the following method:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
return true
}
We can pass any time interval here (for example, if we only want to check once a day). The value we pass in is only defining a minimum amount of time that should pass between checks, however. There is no way to tell the OS a maximum amount of time between checks.
Now, we must implement the method that actually gets called when the OS gives us an opportunity to do background work:
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// do background work
}
We can do whatever we want within this method. There are two catches, however.
This method is called while our app is in the background. The OS limits us to (I believe) thirty seconds. After thirty seconds, our time is up.
We must call the completionHandler() (or the OS will think we used all of our time).
The completionHandler that gets passed in takes an enum, UIBackgroundFetchResult. We should pass it either .Failed, .NewData, or .NoData, depending upon what our actual results were (this approach is typically used for checking a server for fresh data).
So, our method might look like this:
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// do stuff
if let _ = error {
completionHandler(.Failed)
} else if results.count > 0 {
completionHandler(.NewData)
} else {
completionHandler(.NoData)
}
}
Keep in mind, we have absolutely zero control over how frequently the OS will actually let us run this code in the background. The OS uses several metrics in order to optimize the user's experience.
I think if your app reports .Failed to the completion handler, the OS might give you a second chance soon, however if you're abusing .Failed, the OS could probably blacklist your application from using background fetches (and Apple could deny your app).
If your app isn't reporting .NewData, the OS will let your app do background work less often. I'm not saying this because I recommend that you just always report .NewData. You should definitely report accurately. The OS is very smart about scheduling work. If you're passing .NewData when there isn't new data, the OS will let your app work more often than it may need to, which will drain the user's battery quicker (and may lead to them uninstalling your app altogether).
There are other metrics involved in when your app gets to do background work however. The OS is very unlikely to let any app do background work while the user is actively using their device, and it is more likely to let apps do background work while the user is not using their device. Additionally, OS is more likely to do background work while it is on WiFi and while it is plugged into a charger of some sort.
The OS will also look at how regularly the user uses your app, or when they regularly use it. If the user uses your app every day at 6pm, and never at any other time, it's most likely that your app will always get an opportunity to do background work between 5:30pm and 6pm (just before the user will use the app) and never during any other part of the day. If the user very rarely uses your app, it may be days, weeks, or months between opportunities to work in the background.