I think I know the answer to this question already, but I wanted to ask just to be thorough.
Consider the Apple Watch built-in Maps app. When you're using turn-by-turn directions, when it's time to turn left or right, the watch plays a custom haptic pattern - even though the screen is off and the app is backgrounded. Another example is while you're doing a workout - if you've set a goal, you'll get a light tap on your wrist when you get 50% there and 100% there, even if you're not looking at the watch at the time (screen off, app backgrounded).
In watchOS 2, is there any way for us 3rd party developers to have an app play a certain haptic pattern when the screen is off and the app is backgrounded? I know the playHaptic: method works while the app is active to let you play several different kinds of haptic patterns, and I know that while the app is inactive, you can have a notification come in - but the notification would only ever play the 'notification' haptic feeling, no choice in that.
You can only run custom code when you app is active. So I´m afraid you can't do this.
Here is how i'm playing haptic in background,
first of all you need to enable background mod in Capabilities for WatchExtensionand to enable:Workout Processing and Audio,Airplay.
Also you need to enable for WatchExtension HealthKit.
#import < HealthKit/HealthKit.h >
add HKWorkoutSessionDelegate
-(void)awakeWithContext:(id)context{
[super awakeWithContext:context];
HKHealthStore *cwHealthStore = [[HKHealthStore alloc] init];
cwConfiguration = [[HKWorkoutConfiguration alloc] init];
cwConfiguration.activityType = HKWorkoutActivityTypeOther;
NSError *error;
HKWorkoutSession *cwSession = [[HKWorkoutSession alloc] initWithConfiguration:cwConfiguration error:&error];
[cwSession setDelegate:self];
if (!error) {
[cwHealthStore startWorkoutSession:cwSession];
}
[self test];
}
#pragma mark WorkoutSession Delegates
- (void)workoutSession:(HKWorkoutSession *)workoutSession
didChangeToState:(HKWorkoutSessionState)toState
fromState:(HKWorkoutSessionState)fromState
date:(NSDate *)date{
NSLog(#"------>%ld", (long)toState);
}
- (void)workoutSession:(HKWorkoutSession *)workoutSession didFailWithError:(NSError *)error{
NSLog(#"%#", error);
}
And now you can play haptic in background.
-(void)test{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(timerTrick:) userInfo:nil repeats:true];
}
- (void)timerTrick:(NSTimer *)time {
[[WKInterfaceDevice currentDevice] playHaptic:WKHapticTypeStart];
}
Don't forghet to stop workout Session after leaving controller:
[cwHealthStore endWorkoutSession:cwSession];
Just to post an update to my own question a couple years later - in watchOS 3 workout apps were granted background execution, but no haptics (I think).
In watchOS 4, workout apps, audio recording apps and navigation apps have background execution; navigation apps can send haptics in the background. Furthermore, the "frontmost app" (app last used which still appears if wrist is raised within 2 minutes, or 8 if extended frontmost time is enabled) has some privileges for sending haptics at the conclusion of a WatchConnectivity or NSURLSession data transfer, or when a notification comes in. See the docs for details.
Related
I started messing around with the Watch OS framework today and wanted to throw together a quick app, but have come to a couple questions.
I made an iOS app that just shows the current battery % as well as the state of the battery. I then wanted to show that over on the watch.
The only time the watch app will update is when I totally close the iOS app, then open it, while the watch app is active. How do I allow my watch app to be updated if I open it after the iOS app has been opened?
This kind of goes with number 2. But how do I allow the watch app to fetch info from the iOS app, after it has been in the background? As an example, lets say the iOS app has been in the background and I wanted to fetch the battery % without opening the iOS app to the foreground.
Some side notes on how I set this up -
Within the iOS app, in the viewDidLoad method, I start my session.
if ([WCSession isSupported]) {
wcSession = [WCSession defaultSession];
wcSession.delegate = self;
[wcSession activateSession];
}
Then call my method to update the actual battery % and state. Within that method, I have this which sends the info over to the watch:
NSDictionary *message = #{
#"message" : [NSString stringWithFormat:#"%#", [numberFormatter stringFromNumber:levelObj]],
#"message_2" : [NSString stringWithFormat:#"%ld",(long)[UIDevice currentDevice].batteryState],
};
[wcSession sendMessage:message replyHandler:nil errorHandler:^(NSError * _Nonnull error) {
NSLog(#"%#", error.localizedDescription);
}];
I also call this same method in the viewDidAppear, so I don't have to relaunch the app completely, to allow refreshing of the watch counterpart.
On the watch side I have the viewWillActivate method with the same activation as the iOS side as well as the method to handle what the watch app receives from the iOS side. But it will only update when I restart the iOS app fully.
- (void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message {
NSLog(#"Message recieved!");
[_batteryLevelLabelW setText:message[#"message"]];
}
Also in there is the code to handle the battery state message, which is a bit long.
I hope I gave a good amount of information to help.
According to documentation:
Use the sendMessage(_:replyHandler:errorHandler:) or
sendMessageData(_:replyHandler:errorHandler:) method to
transfer data to a reachable counterpart. These methods are intended
for immediate communication between your iOS app and WatchKit
extension. The isReachable property must currently be true for these
methods to succeed.
If watchapp is not foreground, message will not be delivered since isReachable is false.
Method you should use is updateApplicationContext(_:) - it will wait till watch app will be opened at foreground and only then will be delivered.
So, i want my app to do background execution for only a fixed amount of time, this is in case the user does not manually stop the app, and the app therefore in theory could run in background forever(is that even possible?).
I'm using the code below (just a test app) to test how long exactly a background task can run before ending. I read somewhere that 10 minutes is the longest we can do background execution, and there is no way to get beyond that(?). However, my code will only execute in the background for 3 minutes.
So to sum up my questions:
Is it possible to tell the app to execute in the background for x > 10 minutes?
2.Do i have any other options for something similar? (the actual app i need this implemented in, receives location updates in the background, the user could have the phone in the background for as long as 30 minutes, and suddenly not receiving updates would be bad)
- (void)viewDidLoad {
[super viewDidLoad];
counterTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
// do something }];
count=0;
theTimer=[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:#selector(countUp)
userInfo:nil
repeats:YES];
}
- (void)countUp {
if (count==10000) {
[theTimer invalidate];
[[UIApplication sharedApplication] endBackgroundTask:counterTask];
} else {
NSLog(#"asd");
count++;
NSString *currentCount;
currentCount=[[NSString alloc] initWithFormat:#"%d",count];
_theCount.text=currentCount;
long seconds = lroundf([[UIApplication sharedApplication] backgroundTimeRemaining]);
NSLog([NSString stringWithFormat:#"%ld",seconds]);
}
}
I read somewhere that 10 minutes is the longest we can do background execution, and there is no way to get beyond that(?). However, my code will only execute in the background for 3 minutes.
yes you are right before iOS 7 iOS allowed 10 minutes max for apps to execute in background , however since iOS 7 they have reduced this time to 180 seconds.
But if you want to get Location Updates in background than you can add Required Background modes property in your info.Plist file. Using this you will be able to run your app in background for getting location updates Apple will review your request while reviewing your app for app store submission so be sure to use this mode only if you using it for its actual purpose.
Following are various modes for which apple allows background execution you can take a look at it at Apples Doc on background execution
Edit
If you wish to stop getting location Updates after specific time once user goes to backGround you can do this
- (void)applicationDidEnterBackground:(UIApplication *)application {
[self performSelector:#selector(stopGettingLocationUPdates) withObject:nil afterDelay:1800];
}
-(void)stopGettingLocationUPdates{
[self.locationManager stopUpdatingLocation]
}
This will stop updates after 30 mins.
Your code is not running in the background. It is not testing what you want to test.
Apple's docs say:
Executing a Finite-Length Task in the Background Apps that are
transitioning to the background can request an extra amount of time to
finish any important last-minute tasks. To request background
execution time, call the
beginBackgroundTaskWithName:expirationHandler: method of the
UIApplication class.
The actual time you get is not specified and is probably decided ad hoc based on power consumption, memory needs and so on. They may be a maximum.
They go on to say:
Implementing Long-Running Background Tasks For tasks that require more
execution time to implement, you must request specific permissions to
run them in the background without their being suspended. In iOS, only
specific app types are allowed to run in the background:
Apps that play audible content to the user while in the background, such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly
Apps that receive regular updates from external accessories
Apps that implement these services must declare the services they support and use system frameworks to implement the relevant aspects of
those services. Declaring the services lets the system know which
services you use, but in some cases it is the system frameworks that
actually prevent your application from being suspended.
I'm creating an online-shop-style app where users can browse different products on their iPad and order these products. The ordering process consists of creating an xml-file with the user's data and the relevant products he would like to order. But sometimes there might be the case, that users don't have an internet connection right now and I would like to create some mechanism, which checks every x minutes for an active internet connection and then tries to deliver the order-xml. It should repeat this step until it gets connected to the web and then just stop it, when all offline carts have been sent.
I have already been searching the web but only found ways to do this on iOS 7 (with UIBackgroundModes - fetch). But I don't want to use iOS 7 because the app is already done and I'm not planning to redesign it for iOS 7 (it's an Enterprise App). As far as I know, the current Background Execution time on iOS 6 is limited to something like 15 minutes, is that correct?
Any ideas on how to solve that?
Thanks.
EDIT:
I have tried the following in - (void)applicationDidEnterBackground:(UIApplication *)application
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
[[InstanceHolder getInstance] startNetworkTimer];
}];
and here is what should happen next:
- (void) startNetworkTimer{
if ([CommonCode getAllOfflineCartsForClient:nil].count > 0){
NSTimer *pauseTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:#selector(offlineCartLoop:) userInfo:nil repeats:YES];
}
}
- (void) offlineCartLoop:(id)sender{
if([CommonCode isInternetConnectionAvailable]){
[self sendOfflineCarts];
[sender invalidate];
}
}
startNetworkTimer gets called as it should, but then it doesn't call the offlineCartLoop function :-(
EDIT 2:
I think the timer-thing was the problem. I'm now calling the offlineCartLoop function like this:
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperationWithBlock:^{
[[InstanceHolder getInstance] offlineCartLoop:nil];
}];
and changed the offlineCartLoop function to this:
- (void) offlineCartLoop:(id)sender{
if([CommonCode isInternetConnectionAvailable]){
[self sendOfflineCarts];
}else{
[NSThread sleepForTimeInterval:10.0];
[self offlineCartLoop:nil];
}
}
Seems to work, but will this run forever? Is there anything else I need to take care of?
There is no solution to what you want - there is no such thing as being able to periodically check every N minutes in the background unless it is within the time window granted by beginBackgroundTaskWithExpirationHandler.
However that only permits 10 minutes of execution time for iOS6 and earlier, or approximately 3 minutes for iOS7.
You cannot cheat and try and use a background mode if your app does not need it, and even the background modes do not permit you to freely run whenever you want.
Even the new background modes in iOS 7 do not permit you to run on a scheduled basis.
Your best best actually is iOS7 even though you don't want to migrate to iOS7 - the background fetch being the relevant mode (even though you are pushing not fetching). With that background mode you will be able to have the opportunity to execute but not when you decide, only when the OS decides - and the frequency of that depends upon how the user uses your app.
With iOS6 your options are even less restricted.
See iOS: Keep an app running like a service
Basically there just is no such thing as continuous background execution, nor periodic background execution, nor the app deciding when it wants to run when in the background.
If the user does not have an internet connection at the time they use your app to place the order then you should be notifying them of that anyway (if you don't then your app risks rejection from the app store) and maybe tell them to try again later.
If they are in flight mode the user will know they are in flight mode, if there is a temporary interruption (such as the phone is in an elevator or tunnel) then your app could keep on trying for as long as it is able - keep trying every minute while in the foreground, then when you switch to the background you know you have 10 minutes left, keep trying until the 10 minutes has nearly expired then post a local notification to the user notifying them that the app was unable to place the order due to lack of connectivity. If the user clicks on the notification and your app launches then the app will have the chance to retry again at that point.
If you still cannot make a connection then so be it, but you will have the chance to start the retry algorithm again. But at least you have notified the user their order has not gone through.
If what you need to know is if and when a data connection is available, I recommend inverting the process: rather then querying for a data connection, let your app be notified when a data connection is available. It's more efficient.
On this subject, I suggest using Reachability: you can make a call to know if a specific URL is accessible, and execute a block of code as soon as a connection is available.
Reachability *reach = [Reachability reacabilityWithHostName:#"www.myservice.com"];
...
reach.reachableBlock = ^(Reachability *reach) {
// Process the requests queue
// You should implement the method below
[self processQueue];
}
...
if ([reach isReachable]) {
// Upload the XML file to the server
// You should implement the method below
[self uploadToServer:myRequest];
} else {
// Enqueue your request somewhere, for example into an NSArray
// You should implement the method below
[self addToQueue:myRequest];
}
The above code is meant to be a showcase (it doesn't work as is), use it as reference. I can just say that the reach variable should be a class property or data member, and that it should be initialized once.
Also, if you enqueue your requests into an NSArray, be sure to do it in thread safe mode
Alternatively, Reachability can also notify via NSNotification when a connection is available - a different way to achieve the same result. Up to you to decide which one better fits with your needs.
I have a requirement where my app connects to a country channel (USA) and starts playing records from the channel. This is basically a channel which is run by users, the users upload their records to channel and they are played one by one. The user who connects to channel they start listening to channel.
The server sends the iOS app the URLs for the record that needs to be played via sockets, the iOS app creates AVQueuePlayer to play the URL's (using AVPlayerItems) one by one.
If I keep app in background when the channel is full of records for almost 1 day or so, the app keep running and keep playing all the records one by one. I know that AVQueuePlayer takes care of running the app all the time without killing as it receives new player items to play.
But if there are no records in channel and if user connects to channel, then app doesn't play the records in background if the idle time of the app exceeds 10 minutes.
I have written code with background task identifier which keeps my socket connect open so that new record URLs can be received all the time.
I see some of the crash reports in my device which says "AppName(my app) has active assertions beyond permitted time"
So can I know what wrong is going on here.
I am posting the background task code as well
- (void)keepBroadcastPersistentConnection {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if(self._bgTaskIdentifier)
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
self._bgTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{
[[UIApplication sharedApplication] endBackgroundTask:self._bgTaskIdentifier];
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
CGLog(#"========================================end bg task at time %#", [NSDate date]);
CGLog(#"Time taken by app to run in bg is %f seconds", [[NSDate date] timeIntervalSinceDate:self.date]);
}];
[[BroadcastSocketConnecter sharedSocketConnecter].socketIO sendHeartbeat]; // this keep the socket alive
self.date = [NSDate date];
CGLog(#"========================================begin bg task at time %#", self.date);
});
}
Thanks
From the audio session programming guide:
Why a Default Audio Session Usually Isn’t What You Want
Scenario 3. You write a streaming radio application that uses Audio
Queue Services for playback. While a user is listening, a phone call
arrives and stops your sound, as expected. The user chooses to ignore
the call and dismisses the alert. The user taps Play again to resume
the music stream, but nothing happens. To resume playback, the user
must quit your application and restart it.
To handle the interruption of an audio queue gracefully, implement
delegate methods or write an audio session callback function to allow
your application to continue playing automatically or to allow the
user to manually resume playing. See “Responding to Audio Session
Interruptions.”
Shortly, the solution would be to implement the AVAudioSessionDelegate protocol'a beginInterruption and endInterruption methods. However, the delegate property of the AvAudioSession class was deprecated in iOS6 and Notifications should be used instead. Namely, you are interested in the AVAudioSessionInterruptionNotification
Solution. According to this story if the playback stops, then you should explicitly activate the audio session again to prevent your app from being terminated.
Below is the source for the delegate implementation but the logic doesn't change much with the notifications so I feel it's still a good source for info.
- (void) beginInterruption {
if (playing) {
playing = NO;
interruptedWhilePlaying = YES;
[self updateUserInterface];
}
}
NSError *activationError = nil;
- (void) endInterruption {
if (interruptedWhilePlaying) {
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
[player play];
playing = YES;
interruptedWhilePlaying = NO;
[self updateUserInterface];
}
}
Old response which is still valid but not an elegant solution
You cannot start playing audio in the background. This answer explains what I mentioned in my comment above: https://stackoverflow.com/a/16568437/768935 Doing tricks with the AudioSession does not seem to have an effect on this policy.
As a solution, you need to keep playing audio. If there is no item in the queue, then insert the "silence" audio track. However, I have my doubts that the app with this trick will be admitted in the App Store. It may be better to inform the user that audio playback will be resumed on if the app is started again.
I am developing a music player application which is able to play music in the background. As long as the music is playing the application won't get terminated anyway, but if the playback is stopped and the application is in background it may get terminated. To get the application more user-friendly I want to save the playback queue and state when the app is being terminated by the system so I implemented the following lines in the app delegate:
- (BOOL)application:(UIApplication *)application shouldSaveApplicationState:(NSCoder *)coder
{
/*
Archive the current queue controller to be able to continue the playback like the app never has been terminated
when user is launching it again.
*/
SMKQueueController *queueController = [[UIApplication sharedApplication] queueController];
[coder encodeObject:queueController forKey:#"queueController"];
return YES;
}
- (BOOL)application:(UIApplication *)application shouldRestoreApplicationState:(NSCoder *)coder
{
/*
Restore the recently used queue controller to be able to continue the playback like the app never has been
terminated when use is launching it again.
*/
SMKQueueController *queueController = [coder decodeObjectForKey:#"queueController"];
[[UIApplication sharedApplication] setQueueController:queueController];
return YES;
}
Sometimes (especially when I kill it manually via the double-tap-home-button menu) it works as I would expect it to work. But sometimes this methods aren't called when the application is being terminated.
So my question is: Did I misunderstood how the methods work or what they are for? Or is there any better place to implement such a functionality?
I attended a WWDC 2012 session describing exactly how this should be done. If you are a registered Apple developer, then you can access the videos from the sessions. This one is titled, "Saving and Restoring Application State on iOS". I found this to be a very informative session on this topic.