I am working on the iPhone app in Objective-C in which I count steps of user by using CMAccelerometerData.
It works fine when app is running in background ,
But when I kill my app (double-clicking the Home button and then swiping app away) then I can not get the steps count.
The feature of app is that fetch steps count if my app is in kill state (double-clicking the Home button and then swiping app away) and I run it from icon.
Is there any way to get steps count if our app is killed (double-clicking the Home button and then swiping app away) ?
Here is the method to get steps
- (void)startDetectionWithUpdateBlock:(void (^)(NSError *))callback
{
if (self.motionManager.isAccelerometerActive) {
return;
}
[self.motionManager startAccelerometerUpdatesToQueue:self.queue withHandler:^(CMAccelerometerData *accelerometerData, NSError *error) {
if (error) {
if (callback) {
dispatch_async(dispatch_get_main_queue(), ^{
callback (error);
});
}
return ;
}
CMAcceleration acceleration = accelerometerData.acceleration;
CGFloat strength = 1.2f;
BOOL isStep = NO;
if (fabs(acceleration.x) > strength || fabs(acceleration.y) > strength || fabs(acceleration.z) > strength) {
isStep = YES;
}
if (isStep) {
if (callback) {
dispatch_async(dispatch_get_main_queue(), ^{
callback (nil);
});
}
}
}];
}
And through this way I update steps count :
[[SOLocationManager sharedInstance] start];
[[SOStepDetector sharedInstance] startDetectionWithUpdateBlock:^(NSError *error) {
if (error) {
NSLog(#"%#", error.localizedDescription);
return;
}
self.actualCheckpoint.stepsCount ++;
NSLog(#"Total Steps: %ld",(long)self.actualCheckpoint.stepsCount);
[[NSNotificationCenter defaultCenter] postNotificationName:SBCompetitionStepsCountDidChange object:self.actualCheckpoint];
}];
you can catch the data in appDelegate's
- (void)applicationWillTerminate:(UIApplication *)application {}
You can't access HealthKit data in the background when the device is locked, according to Apple:
For security, the HealthKit store is encrypted when the device is locked. The HealthKit store can only be accessed by an authorized app. As a result, you may not be able to read data from the store when your app is launched in the background; however, apps can still write data to the store, even when the phone is locked. HealthKit temporarily caches the data and saves it to the encrypted store as soon as the phone is unlocked.
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/
You'll need to read the data on startup to 'catch up' from the last time your app was run.
If the app is killed by the user by swiping up, rather than by simply putting the app into the background, it is no longer allows to execute any code until the user opens it again. However, there's no way to get direct access to the accelerometer if the app is completely killed.
There are ways to handle keeping the app open indefinitely if it goes into the background (not manually killed by the user). However, having the accelerometer powered up constantly would be a significant battery drain, and if all you want to do it track steps, you can simply use the HealthKit API. (If you need your app to work on old devices, you will have to resort to background processing.)
HealthKit
As an Apple OS-level API, HealthKit is always working and (if the user's device supports it and the user hasn't disabled it) is constantly tracking steps in the background, using Apple's accurate, battery efficient algorithm (it uses the device's motion coprocessor).
All you need to do is integrate your app with HealthKit such that it can read the device's Steps metric. Take a look at this objective-C tutorial.
Related
We're using the RMStore framework (https://github.com/robotmedia/RMStore) in our game app to do In-App Purchases on iOS.
Sometimes between issuing the In-App Purchase and getting a feedback (showing the system popup to start the Purchase) takes up to 10 seconds. This is kind of rare but on some days it's really bad, while on others it's very fast.
Is there anything we can do about to have it always fast?
Do you/others also experience this issue?
Here's the code we use to start a purchase:
[[RMStore defaultStore] addPayment: [NSString stringWithUTF8String:productIdentifier.c_str()] success: ^(SKPaymentTransaction *transaction) {
this->onPurchaseInAppProductSuccessInner(productIdentifier, [=](InAppPurchaseProductResult result) {
callback(result);
});
} failure:^(SKPaymentTransaction *transaction, NSError *error) {
MLLOG("[AccountManager] Purchase Error");
}];
In my iOS app I turned on location services in background and also set on always. I also set up region monitoring for every 500 meters so if iOS kills my app in background then it will wake up using region monitoring.
But I found one major issue in updating location. I disabled location services of iOS and re enable it, my app is still in background but it's location icon showing disable and other app which is not even in background shows enable. Please see attached screenshot.
If any one knows about this then please guide me.
I think you have wrong concept, the arrow unfilled means that the app is using geofences, if you scroll to the bottom in that list you can read the legend as you can se in this screenshot
Hope this helps you
In app delegate.m,
- (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions
{
[self setUpLocationManager];
return YES;
}
#pragma mark - CLLocationManager Delegate
-(void)setUpLocationManager
{
LocationManager=[[CLLocationManager alloc]init];
LocationManager.delegate=self;
LocationManager.desiredAccuracy=kCLLocationAccuracyBest;
[LocationManager requestWhenInUseAuthorization];
[LocationManager startUpdatingLocation];
}
-(void)locationManager:(CLLocationManager )manager didUpdateLocations:(NSArray<CLLocation > *)locations
{
CLGeocoder *GioCoder=[[CLGeocoder alloc]init];
[GioCoder reverseGeocodeLocation:LocationManager.location completionHandler:^(NSArray<CLPlacemark > Nullable placemarks, NSError * Nullable error)
{
if (error !=nil)
{
}
else
{
CLPlacemark *place=[placemarks lastObject];
NSLog(#"%#",place);
NSString *Path=[[NSBundle mainBundle]pathForResource:#"countries" ofType:#"json"];
NSError *Error;
NSData *JsonData=[[NSData alloc]initWithContentsOfFile:Path options:NSDataReadingMappedAlways error:&Error];
NSMutableArray *DataArray=[[NSMutableArray alloc]init];
if (Error==nil)
{
NSError *err;
DataArray=[NSJSONSerialization JSONObjectWithData:JsonData options:NSJSONReadingMutableContainers error:&err];
}
for (int i=0; i<DataArray.count;i++)
{
if ([[[DataArray objectAtIndex:i]valueForKey:#"code"] isEqualToString:place.ISOcountryCode])
{
// Insert your code here
[LocationManager stopUpdatingLocation];
break;
}
}
}
}];
}
-(void)locationManager:(CLLocationManager )manager didFailWithError:(NSError )error
{
if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusDenied || [CLLocationManager authorizationStatus] == kCLAuthorizationStatusRestricted)
{
NSLog(#"Location not enabled in your device");
}
else
{
}
}
Just as Reiner said in his answer, your understanding is wrong. If you have an empty purple arrow it means your geofence is set correctly.
If another app is staying filled purple. It could be because of multiple reasons:
It has pausesLocationUpdatesAutomatically set to false and is always tracking.
It has pausesLocationUpdatesAutomatically set to true but hasn't yet paused long enough at a location to trigger a pause which would technically turn that filled purple into either gray or empty purple (which means the app is continuing tracking using significant location changes or regionMonitoring or visits monitoring)
AFAIK if you region is setup for 500meters then it's better to use SignificantLocation Changes. Because you can use cellTower information. For regionMonitoring app turns it's location Tracking on and off every few minutes to make sure it's been exited the app or not.
VERY IMPORTANT NOTE
Apple documents on this are very confusing. It took me a whole week to figure it out!
Region Monitoring or significant location Tracking or visit Monitoring will only launch app in the background if and only if your app was terminated. If your app is launched again then right from the DidFinishLaunching you can startLocationUpdates.
If your app was suspended or for some reason still in the background you would only get the didExitRegion callback. And AFIAK from the callback you're not able to startUpdateLocations. This is a very vague text from WWDC.
Finally, you must start your location updates while in the
foreground. If you don’t start your updates in the foreground, you
won’t get this special behavior. So what happens if you do start your
updates in the background? Well, first off, your app will probably
need AlwaysAuthorization since your app won’t be considered in use at
that time. Furthermore, Core Location won’t take any action to
ensure your app continues to run. So if you have background runtime
for some reason, and you decide to start a location session, you
might get some updates, but you might also get suspended before you receive all the information you had hoped to receive.
WWDC 2016: Session 716
I don't know why Apple is saying might...it could be something that varies based on battery/cell coverage/ usage patterns / iOS version. It's obviously something they don't want us developers know how. They want to force us to go for other ways.
The only way for you to get your app into terminated state is either if you user manually kills the app or you wait for a very very long time ie wait for any or all other background tasks to finish + then once app is suspended wait for it to become terminated and then exit the region to launch app and be able to start tracking Locations
I use the code below to have my app detect updates to HealthKit data in the background. Will the init method of my AppDelegate be called when this code is run in the background? What methods in the AppDelegate will be called? If someone can provide documentation about the application lifecycle of background code, that will be much appreciated!
[healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
if (success) {
HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:type
predicate:nil
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if (!error) {
[self retrieveHealthDataWithCompletionHandler:completionHandler];
}
}];
[healthStore executeQuery:observerQuery];
}
A bit late but hopefully, it would still help you or anyone else who reach here..
When your app delegate’s application:didFinishLaunchingWithOptions: method is being called you can assume the app launches. Thats why Apple recommend you'll register any observer queries you'd like to have inside that method.
When there will be new data of the type you registered for, HealthKit will wake your app. (So far you still don't know anything about any new data.) Once your app finish its launching it will call the beloved app delegate’s application:didFinishLaunchingWithOptions: method, which as stated before, should contain the code of registering the observer queries.
Once you register your queries, next thing will be getting an update about new data (this is the purpose of observer queries).
Getting the update about something new in HealthKit doesn't contain the data itself. Thats why in the updateHandler of the observer query you should initiate anther query - a more specific one that will fetch the wanted data.
That's it. I would make some changes in the code you provided in order for it to work:
[healthStore enableBackgroundDeliveryForType:type frequency:HKUpdateFrequencyHourly withCompletion:^(BOOL success, NSError *error) {
if (success) {
//Nothing much to do here
}
}];
HKObserverQuery *observerQuery = [[HKObserverQuery alloc] initWithSampleType:type
predicate:nil
updateHandler:^(HKObserverQuery *query, HKObserverQueryCompletionHandler completionHandler, NSError *error) {
if (!error) {
//Create and execute a query about the sample type.
// Within the completion handler of the new query, don't forget to call completionHandler()
}
}];
[healthStore executeQuery:observerQuery];
You can find more details here:
Receiving Background Deliveries
Apps can also register to receive updates while in the background by calling the HealthKit store’s enableBackgroundDeliveryForType:frequency:withCompletion: method. This method registers your app for background notifications. HealthKit wakes your app whenever new samples of the specified type are saved to the store. Your app is called at most once per time period defined by the frequency you specified when registering.
As soon as your app launches, HealthKit calls the update handler for any observer queries that match the newly saved data. If you plan on supporting background delivery, set up all your observer queries in your app delegate’s application:didFinishLaunchingWithOptions: method. By setting up the queries in application:didFinishLaunchingWithOptions:, you ensure that the queries are instantiated and ready to use before HealthKit delivers the updates.
After your observer queries have finished processing the new data, you must call the update’s completion handler. This lets HealthKit know that you have successfully received the background delivery.
I'm implementing a system based on firebase docs:
[connectionMonitor observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
if([snapshot.value boolValue]) {
// connection established (or I've reconnected after a loss of connection)
// add this device to my connections list
// this value could contain info about the device or a timestamp instead of just true
Firebase * con = [[Firebase alloc]initWithUrl:[NSString stringWithFormat:#"%#Users/%#/connections/", urlString, currentUserId]];
Firebase * newConnection = [con childByAutoId];
[newConnection setValue:#YES];
// when this device disconnects, remove it
[newConnection onDisconnectRemoveValue];
}
}];
Which works fine, if the user fully disconnects, but that's my problem.
I use this system to see if the user is online. If they're not online, I trigger a push notification. If the user closes the app, firebase doesn't disconnect, but it also doesn't receive updates, so on the other end, the user looks like they are still online. For the firebase onDisconnect value to properly set, the user is required to completely close out of the app.
I have resolved this by adding:
- (void)applicationWillResignActive:(UIApplication *)application
{
[Firebase goOffline];
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[Firebase goOffline];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[Firebase goOnline];
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[Firebase goOnline];
}
Is this normal behavior, or am I doing something wrong?
This is (currently) expected behavior. Firebase won't trigger the presence actions until the client actually disconnects and iOS will leave the underlying socket connection alive for some period of time (probably less than 5 minutes) after the app goes to the background... so presence will be delayed. It should still definitely happen eventually though.
Your workaround should work fine, or to avoid tearing down the whole connection, you could just set the presence bit to #NO / #YES on going to background / foreground.
I can see how most apps would expect presence to kick in when the app goes to the background, so we may investigate changing this behavior in the future.
I'm trying to get local matchmaking working in GameKit using [[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:]. Essentially, I'm trying to implement interface-less local matches: as long as there's a player in my local vicinity, I want to connect and start a match. Importantly, I only want to do this for local players: I never want to match automatically over the internet.
I've enabled Game Center for my app in iTunes connect and signed up for a different sandbox account on every device I'm using to test.
I've tried both matchmaking with GKMatchmakerViewController (after authenticating the local player) and programmatic matchmaking with startBrowsingForNearbyPlayersWithReachableHandler:, running the same code on an iPhone 4 and an 4th gen iPod Touch sitting next to each other on my desk. Neither ever finds the other; when using GKMatchmakerViewController the interface for finding nearby players remains at the
Finding Players...
spinner, and when using startBrowsingForNearbyPlayersWithReachableHandler:, the notification block never gets called.
Here's my current block of testing code:
-(void)connectLocal
{
if( ![GKLocalPlayer localPlayer].isAuthenticated )
{
// authenticateLocalPlayer calls connectLocal again after authentication is complete
[ self authenticateLocalPlayer ];
return;
}
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:^(NSString *playerID, BOOL reachable) {
NSLog(#"Reachability changed for player %#", playerID );
} ];
}
The docs are a little sparse & confusing on the subject, especially when it comes to the difference between local mulitplayer and matches over the internet. For instance, it seems to be necessary to authenticate the local player and create a match before finding players to join that match (Creating Any Kind of Match Starts with a Match Request). However this little nugget seems to suggest otherwise:
The standard user interface allows players to discover other nearby players, even when neither player is currently connected to Game Center directly.
Additionally, in the flow described in Searching For Nearby Players, a match request isn't created until step 3, after finding players via the notification block passed to startBrowsingForNearbyPlayersWithReachableHandler:. Unfortunately, I've never got that far.
So, the questions:
1) Am I right in thinking I can call startBrowsingForNearbyPlayersWithReachableHandler: before authenticating the local player? GameKit doesn't throw an error, so I'm assuming it's OK. This may be a rash assumption. Whether I authenticate or not doesn't seem to make much difference.
2) Has anyone successfully implemented local auto-matching using [GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:? Is there good example code anywhere that illustrates the complete flow, from browsing for players to starting a match, all programmatically?
3) There seem to be conflicting reports on the web over whether GameKit-enabled apps can be tested in the iOS Simulator. General consensus seems not, and it's better to test on iOS hardware. I've been using a iPhone 4 & an 4th gen iPod Touch. For those who have successfully tested local multiplayer, what testing setup & methodology did you use?
1) Am I right in thinking I can call startBrowsingForNearbyPlayersWithReachableHandler: before authenticating the local player?
No. startBrowsingForNearbyPlayersWithReachableHandler works by both advertising the existing player and browsing for other players but, most importantly, the information it uses to identify players is the playerID... which won't be available until the player authenticates.
3) There seem to be conflicting reports on the web over whether GameKit-enabled apps can be tested in the iOS Simulator. General consensus seems not, and it's better to test on iOS hardware
GameCenter authentication, achievements, and leaderboards work in the simulator, everything else should be tested on real hardware. I actually recommend the simulator for authentication testing, since it avoids the sandbox/production switch which can make detailed auth testing on devices somewhat confusing. Everything else can only be tested on devices. The simulator doesn't have great support for receiving push notifications which breaks match setup and the general hardware configuration is different enough that match communication isn't unlikely to work right anyway.
You need to do these things in this order:
Authenticate the local player
Install an invite handler
Start browsing for nearby players
Authentication is required - this registers your app with Game Center and logs the player in. In most cases, you won't even need internet access to do this.
Installing the invitation handler is also required, and I think this is the step you're missing. This lets your app know what to do when an inbound invitation is received. If you don't do this, a device won't register as being nearby.
Only start browsing once you've done the above two.
Here's some sample code to get you going. Call this method after the app launches:
- (void) authenticateLocalPlayer
{
static BOOL gcAuthenticationCalled = NO;
if (!gcAuthenticationCalled) {
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
void (^authenticationHandler)(UIViewController*, NSError*) = ^(UIViewController *viewController, NSError *error) {
NSLog(#"Authenticating with Game Center.");
GKLocalPlayer *myLocalPlayer = [GKLocalPlayer localPlayer];
if (viewController != nil)
{
NSLog(#"Not authenticated - storing view controller.");
self.authenticationController = viewController;
}
else if ([myLocalPlayer isAuthenticated])
{
NSLog(#"Player is authenticated!");
//iOS8 - register as a listener
[localPlayer unregisterAllListeners];
[localPlayer registerListener:self];
[[GKLocalPlayer localPlayer] loadFriendPlayersWithCompletionHandler:^(NSArray *friendPlayers, NSError *error) {
//Do something with the friends
}];
//iOS7 - install an invitation handler
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
// Insert game-specific code here to clean up any game in progress.
if (acceptedInvite)
{
//This player accepted an invitation.
//If doing programmatic matchmaking, call GKMatchmaker's matchForInvite:completionHandler
//to get a match for the invite. Otherwise you need to allocate a GKMatchmakerViewController
//instance and present it with the invite.
}
else if (playersToInvite)
{
//Your game was launched from the GameCenter app to host a match.
}
};
//Now you can browse. Note this is the iOS8 call. The iOS7 call is slightly different.
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithHandler:^(GKPlayer *player, BOOL reachable) {
NSLog(#"Player Nearby: %#", player.playerID);
}];
}
else
{
//Authentication failed.
self.authenticationController = nil;
if (error) {
NSLog([error description], nil);
}
}
};
localPlayer.authenticateHandler = authenticationHandler;
gcAuthenticationCalled = YES;
}
}
* IMPORTANT *
If you're using iOS8, you don't install the invitation handler. You instead register an object as listening for the protocol GKLocalPlayerListener, and implement these methods:
-player:didAcceptInvite:
-player:didRequestMatchWithRecipients:
If you don't implement these methods on iOS8, it won't work!
You then link GKMatchmaker to that object by calling this after authenticating the local player:
[localPlayer registerListener:self];
Make sure the object that's implementing the protocol is declared like so in the .h file:
#interface MyImplementingObject : NSObject <GKMatchDelegate, GKLocalPlayerListener>
If you do all this and it still isn't working, make sure that you have your bundle ID set correctly in your app (Click the app, click 'Targets', make sure Bundle Identifier and Version are filled in), then click the 'Capabilities' tab (XCode 6), and make sure Game Center is on.
Go to the Member Center and make sure that the app using that bundle ID also has Game Center enabled for its Provisioning Profile. Download and reapply your Provisioning Profile if necessary.
Make sure the sandbox switch is ON in your Settings under GameCenter, and also make sure that 'Allow Invites' and 'Nearby Players' switches are turned ON.
Finally, make sure you go to iTunes Connect and verify that Game Center is enabled for your app there as well.
So, keep in mind the differences between iOS7 and iOS8. This code will work on either version and call 'updateNearbyPlayer' in turn.
if ( IS_IOS8 )
{
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithHandler:^(GKPlayer *gkPlayer, BOOL reachable)
{
NSLog(#"PLAYER ID %# is %#",gkPlayer.playerID,reachable?#"reachable" : #"not reachable");
[self updateNearbyPlayer:gkPlayer reachable:reachable];
}];
} else {
/*
* iOS 7...
*/
[[GKMatchmaker sharedMatchmaker] startBrowsingForNearbyPlayersWithReachableHandler:^(NSString *playerID, BOOL reachable)
{
NSLog(#"PLAYER ID %# is %#",playerID,reachable?#"reachable" : #"not reachable");
[GKPlayer loadPlayersForIdentifiers:#[playerID] withCompletionHandler:^(NSArray *players, NSError *error) {
NSLog(#"Loaded: %#, error= %#",players,error);
GKPlayer *gkPlayer = [players objectAtIndex:0];
[self updateNearbyPlayer:gkPlayer reachable:reachable];
}];
}];
}
With some delay due to the underlying Bonjour services, this mechanism works great. However, it needs to be balanced with an appropriate call to:
[[GKMatchmaker sharedMatchmaker] stopBrowsingForNearbyPlayers];
Whenever you use one of the GKPlayers/PlayerIDs reported to establish a match or to add it to an existing match, you should stop browsing. Once the match has been finished, closed or cancelled, start browsing again. Otherwise, on the second attempt to connect to a nearby device, you'll fail.