Swift CoreMotion background saving battery - ios

I need to get users activity on background using Core Motion. I was able to reach this goal by adding the locations update as background mode and enable the background location fetch. But as I'm not interest in the user location, this results in waste of battery and a blue sign ( iPhone X ) that indicates the app is updating your locations in background.
Is possible to run core motion in background without update the location of the user in order to use less battery and not show the blue sign to the user??
Thank you really much in advance!!
EDIT
Example function code:
private func startTrackingActivityType() {
activityManager.startActivityUpdates(to: OperationQueue.main) {
[weak self] (activity: CMMotionActivity?) in
guard let activity = activity else { return }
DispatchQueue.main.async {
if activity.walking {
self?.activityTypeLabel.text = "Walking"
} else if activity.stationary {
self?.activityTypeLabel.text = "Stationary"
} else if activity.running {
self?.activityTypeLabel.text = "Running"
} else if activity.automotive {
self?.activityTypeLabel.text = "Automotive"
}
}
}
}

You don't need to be in background at all. Next time your app will launch, you can retrieve history for MotionActivity. In addition you can also get raw historical sensor data. But you should schedule it.
Details: https://developer.apple.com/documentation/coremotion/cmmotionactivitymanager?language=objc

Related

HKObserverQuery in Background mode

I have an application that need to track user heart rate readings from apple watch, so I did all the required steps that I found on apple guides, and here is the code that I am using:
static var query: HKObserverQuery?
func startObservingHeartRate() {
guard let heartRateSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
fatalError("Unable to create a step count sample type")
}
AppDelegate.query = HKObserverQuery(sampleType: heartRateSampleType, predicate: nil, updateHandler: { (query, completionHandler, error) in
if error != nil {
// Perform Proper Error Handling Here...
print("An error occured while setting up the Heart Rate observer.")
}
//Read the last strored heatt rate in add it to the DB
//Add last fetched Heart Rate reading to DB and send it to clips
HealthKitManager().fetchLastStoredHeartRate(completion: { (lastReading, error) in
guard let lastReading = lastReading else {
//There is no heart readings in HealthKit
return
}
//Check if Last HR value is Abnormal
if lastReading.doubleValue > 60 {
//TODO: - Schedule notification
if UIApplication.shared.applicationState == .background {
} else {
//TODO: - Show popup to the user
}
}
})
completionHandler()
})
healthKitStore.execute(AppDelegate.query!)
configureHeartRateObserver()
}
func configureHeartRateObserver() {
guard let heartRateSampleType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.heartRate) else {
fatalError("Unable to create a step count sample type")
}
healthKitStore.enableBackgroundDelivery(for: heartRateSampleType, frequency: HKUpdateFrequency.immediate) { (success, error) in
if success {
print("Enabled background delivery of Heart Rate changes")
} else {
print("Failed to enable background delivery of weight changes. ")
}
}
}
and I am calling "startObservingHeartRate" in didFinishLaunchingWithOptions in AppDelegate, assuming that this query should be executed once a new reading added or deleted from the health kit store, every thing is fine, if app is in background or killed the handler wake up my app and it do the updates.
But whenever I put the app in background then put it in foreground again it execute the observer query for many times even if there is no new readings added to the HealthKit store and in this case I am getting the same last heart rate for many times for no reason.
Please any recommendation on how to use this types of query or any changes I need to do with my current implementation.
If you want to track added and removed heart rate samples more precisely, you should use an HKAnchoredObjectQuery. HKObserverQuery does not guarantee that its update handler will only be called when a sample is added or removed. Note that you must continue executing an HKObserverQuery in addition to HKAnchoredObjectQuery since you are also using enableBackgroundDelivery(for:frequency:completion:).

Firebase Storage download task is not completing after the app has spent some time in the background

I am downloading an image from Firebase storage as follows:
let storage = FIRStorage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference(forURL: "MY_STORAGE_URL")
let imageRef = storageRef.child("Path_to_image")
// Download image in memory
let downloadTask = imageRef.data(withMaxSize: 1 * 1024 * 1024) {
(data, error) -> Void in
if (error != nil) {
//Handle the error
} else {
guard let imageData = data else {
print("Unable to unwrap image data.")
return
}
let downloadedImage = UIImage(data: imageData)
//Do some stuff with the image
}
}
I am also monitoring what happens with the download using the following observers:
// Observe changes in status
downloadTask.observe(.resume) { (snapshot) -> Void in
// Download resumed, also fires when the download starts
}
downloadTask.observe(.pause) { (snapshot) -> Void in
// Download paused
}
downloadTask.observe(.progress) { (snapshot) -> Void in
// Download reported progress
}
downloadTask.observe(.success) { (snapshot) -> Void in
// Download completed successfully
}
downloadTask.observe(.failure) { (snapshot) -> Void in
//Download failed
}
This all works just fine when the app is first started. However, I am getting problems if the app enters the background and I play around with some other applications (Facebook, Twitter, etc.), then bring the app back to the foreground. I also have problems if I leave the app open and running in the foreground for greater than or equal to 1 hour.
The problem is that the completion handler in let downloadTask = imageRef.data(withMaxSize: blah blah blah (in the first block of code above) is never called. If the completion handler is never called, I can never unwrap the data and attempt to use the image in my application.
Also, in the downloadTask observers, the only completion handlers that get fired are .resume and .progress. The .success or .failure events are never triggered. This seems to be a Firebase Storage bug to me, but I am not sure. Has anyone else encountered a similar issue? I don't understand why the code would work just fine from a fresh launch, but then after some time in the foreground or after some time in the background the image download stops working. Thanks in advance for any input you may have.
This is currently the expected behavior, unfortunately. Firebase Storage (at present) is foreground only: if the app is backgrounded, we haven't persisted the upload URL, and can't upload in the background nor restart it after it gets out of the background, so it probably is killed by the OS and the item isn't uploaded.
It's The Next Big Thing™ we'd like to tackle (our Android SDK makes it possible, though not easy), but unfortunately for now we haven't made more progress on this.
As a bit of a side note, your observers won't exist after the activity change--downloadTask is gone once the app is backgrounded, so when it comes back into the foreground, we basically need a method that retrieves all tasks that are currently backgrounded, and allows you to hook observers back up. Something like:
FIRStorage.storage().backgroundedTasks { (tasks) -> Void in
// tasks is an array of upload and download tasks
// not sure if it needs to be async
}

Gathering Accelerometer data in background

I'm writing an app, that has to do some calculations every time the phone moves. I've read every question here, but couldn't get the Accelerometer to gather data in the background (after the user navigates away from the app). I've set the Location updates flag in the Plist.info. This is the code I'm using:
let motionManager = CMMotionManager()
func startMotionUpdates() {
var timestamps = [NSDate]()
if motionManager.accelerometerAvailable {
motionManager.accelerometerUpdateInterval = 1
motionManager.startAccelerometerUpdatesToQueue(NSOperationQueue(), withHandler: { (data: CMAccelerometerData?, error: NSError?) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let time = NSDate(timeIntervalSince1970: data!.timestamp)
timestamps.append(time)
print(timestamps)
})
})
}
}
I tried every combination out there, I've tried using CMDeviceMotion, I've tried using CLLocationManager.startUpdatingLocation() to simulate background activity, but nothing works. Does anybody have any ideas?
You cannot be woken up every time the accelerometer changes. You can be woken up whenever the location of the device changes significantly (at least several meters) using CLLocationManager, and that's what "location" means in the background modes.
To track finer-grained motion information, you need to ask the system to start recording the data using CMSensorRecorder, and then later you can ask for the data and compute what you want from it. But you won't be allowed to run in the background continuously watching every jiggle of the device. That would eat too much battery.
See also CMPedometer which addresses certain use cases more directly.

Setting a subview.hidden = false locks up UI for many seconds

I'm using a button to populate a UIPickerView on a hidden UIVisualEffectView. The user clicks the button, the VisualEffectView blurs everything else, and the PickerView displays all the names in their contact list (I'm using SwiftAddressBook to do this.)
This works fine except when the user clicks the button, the UI locks up for about 5-10 seconds. I can't find any evidence of heavy CPU or memory usage. If I just print the sorted array to the console, it happens almost immediately. So something about showing the window is causing this bug.
#IBAction func getBffContacts(sender: AnyObject) {
swiftAddressBook?.requestAccessWithCompletion({ (success, error) -> Void in
if success {
if let people = swiftAddressBook?.allPeople {
self.pickerDataSource = [String]()
for person in people {
if (person.firstName != nil && person.lastName != nil) {
//println("\(person.firstName!) \(person.lastName!)")
self.pickerDataSource.append(person.firstName!)
}
}
//println(self.pickerDataSource)
println("done")
self.sortedNames = self.pickerDataSource.sorted { $0.localizedCaseInsensitiveCompare($1) == NSComparisonResult.OrderedAscending }
self.pickerView.reloadAllComponents()
self.blurView.hidden = false
}
}
else {
//no success, access denied. Optionally evaluate error
}
})
}
You have a threading issue. Read. The. Docs!
requestAccessWithCompletion is merely a wrapper for ABAddressBookRequestAccessWithCompletion. And what do we find there?
The completion handler is called on an arbitrary queue
So your code is running in the background. And you must never, never, never attempt to interact with the user interface on a background thread. All of your code is wrong. You need to step out to the main thread immediately at the start of the completion handler. If you don't, disaster awaits.

Location service going to “Inactive” state in iOS 8

I am creating an app which tracks user. When user login to app I have started significant Location updates. Then i put my application in background. and then started traveling with my car. After 500m-1km I got significant location change. On getting update I have stopped significant location updates and started standard GPS. it gives m location updates for 2-3 min after that i see this "Location icon should now be in state 'Inactive'" and my app does not get any location updates.
If app is foregrounded then again app starts getting location updates.
I am using shared location instance. I have iPhone 5 and iOS 8.1.3. I am using Swift.
Adding some code.. I have done like this
gpsManager = CLLocationManager()
gpsManager.delegate = self
gpsManager.desiredAccuracy = kaccuracy
gpsManager.distanceFilter = kdistanceFilter
gpsManager.pausesLocationUpdatesAutomatically = false
gpsManager.headingFilter = kheadingFilter
gpsManager.activityType = kactivityType
isTracking = false
isMonitoringSignificantChanges = false
//on login
var sharedGPSManager=GeoLocation.sharedInstance
sharedGPSManager.delegate=self
let status=sharedGPSManager.authorizationStatus()
if(status == 0)
{
NSLog("Allowed to track")
}
else
{
NSLog("Not allowed")
}
sharedGPSManager.startSignificantLocationTracking()
func gpsManager(manager:GeoLocation, didUpdateLocations locations:AnyObject)
{
var sharedGPSManager=GeoLocation.sharedInstance
sharedGPSManager.stopSignificantLocationTracking()
task=UIApplication.sharedApplication().beginBackgroundTaskWithName("wifiUpload", expirationHandler: { () -> Void in
}
sharedGPSManager.delegate = self
let status = sharedGPSManager.authorizationStatus()
if(status == 0)
{
NSLog("Allowed to track")
}
else
{
NSLog("Not allowed")
}
sharedGPSManager.startTracking()
//process location data
endBackgroundTask(task)
}
// Start location tracking
func startTracking(){
if(isMonitoringSignificantChanges){
gpsManager.stopMonitoringSignificantLocationChanges()
}
gpsManager.stopUpdatingLocation()
if CLLocationManager.headingAvailable()
{
gpsManager.startUpdatingHeading()
}
gpsManager.startUpdatingLocation()
isTracking=true
isMonitoringSignificantChanges=false
}
Finally I got the Answer on Apple developer forum.keeping significant Location always on worked for me....
You need to add Location Update capability in background mode
Project Target > Capabilities > Turn Background mode ON > Tick all which is applicable for your app
You have to enable Required background modes in info.plist as "App registers for location updates" then your application will give you location updates in background.
And you have to use self.locationManager.startUpdatingLocation() not the significant Location updates.

Resources