m7/m8 detect if motion activity authorization was denied - ios

I am using M7/M8 chip's MotionActivity in a variety of ways, including for step counting. For step counting, I both query for the day's steps, and request ongoingly the step count as they occur realtime.
Currently before I do this I check [CMStepCounter isStepCountingAvailable], as well as a local override flag, before proceeding with this code. I assumed isStepCountingAvailable would return FALSE if authorization for motionActivity was not granted. This does not seem to be the case, it appears to be more of a hardware detection only. I cannot seem to find other methods that detect whether authorization was granted for this.
What this means is that startStepCountingUpdatesToQueue and queryStepCountStartingFrom both run, and return blocks, but always return an error code. Specifically CMErrorDomain code 105.
Is there a better way for me to determine if motionActivity has not been authorized? I've got some fallback code but I'd prefer a boolean check beforehand, instead of an error code in the return block.
if (self.useM7IfAvailable && [CMStepCounter isStepCountingAvailable]){
self.cmStepCounter = [[CMStepCounter alloc] init];
[self.cmStepCounter startStepCountingUpdatesToQueue:self.operationQueue updateOn:1.0 withHandler:^(NSInteger numberOfSteps, NSDate *timestamp, NSError *error){
if(!error){
// do something with numberOfSteps
} else {
// not authorized: CMErrorDomain code 105
}
}];
}
[self.cmStepCounter queryStepCountStartingFrom:dayStart to:dayEnd toQueue:_operationQueue withHandler:^(NSInteger numberOfSteps, NSError *error) {
if(!error){
// do something with numberOfSteps
} else {
// not authorized: CMErrorDomain code 105
}
}];

You're doing it correctly by checking for the error. Per the docs (https://developer.apple.com/library/ios/documentation/coremotion/reference/cmmotionmanager_class/index.html#//apple_ref/c/tdef/CMError) you'll receive back CMErrors with Error Code 105 like you've seen.
Unfortunately there's no way to check ahead-of-time to see if you're authorized or not, but this follows Apple's paradigms with other Core-level frameworks that require authorization, like CoreLocation. The reasoning is you can be in the middle of getting motion steps while in the background, and the user can then disable your motion access, which you'll have to react to that event in probably the same way you'd react to not being authorized in the first place.

Related

removeTracksFromPlaylist not removing tracks with ios spotify sdk

I was testing this method to remove tracks from a playlist. Basically I modified the demo project "simple track playback" provided with the SDK. I wanted to remove the track form the playlist when you hit fastForward. I changed the fastForward method this way but it's not doing anything, and error is nil.
-(IBAction)fastForward:(id)sender {
if([self.player isPlaying] && self.currentPlaylistSnapshot){
SPTAuth *auth = [SPTAuth defaultInstance];
[self.currentPlaylistSnapshot removeTracksFromPlaylist:#[self.player.currentTrackURI]
withAccessToken:auth.session.accessToken
callback:^(NSError *error) {
if (error != nil) {
NSLog(#"*** Failed to remove track : %#", self.titleLabel.text);
return;
}
}];
}
[self.player skipNext:nil];
}
self.currentPlaylistSnapshot is the one I've got from the handleNewSession method.
There's also a static method apparently offering something similar which I have't tried yet.
createRequestForRemovingTracks:fromPlaylist:withAccessToken:snapshot:error:
According to the documentation both options are implemented asynchronously and will take seconds to reflect the results in the server but I'm suspecting that there's either something wrong or I'm just missing to do an actual request to push the changes on the local snapshot maybe?
Documentation:
https://developer.spotify.com/ios-sdk-docs/Documents/Classes/SPTPlaylistSnapshot.html#//api/name/removeTracksWithPositionsFromPlaylist:withAccessToken:callback:
ios sdk:
https://github.com/spotify/ios-sdk
I solved my issue by reseting simulator + adding SPTAuthPlaylistModifyPublicScope (which I fogot to do...)
auth.requestedScopes = #[SPTAuthStreamingScope, SPTAuthPlaylistModifyPublicScope];

iOS - programatically rejecting permission to contacts (after user has accepted)

Is there a way to programatically reject/disable/disallow/reject permissions to access the calendar?
I'm using the following code to ask for / determine permissions:
//request access
[[MyCalendarUtil sharedManager] requestAccess:^(BOOL granted, NSError *error) {
/* This code will run when uses has made his/her choice */
if(error)
{
// display error message here
_calendarLabel.text = #"Calendar OFF";
}
else
if(!granted)
{
// display access denied error message here
_calendarLabel.text = #"Calendar OFF";
}
else
{
// access granted
_calendarLabel.text = #"Calendar ON";
}
}];
Can I disable the permission later on upon a user pressing a button ... ?
No way, its impossible. Take it granted. I will give 1 Million dollars even if you find a private API. :)
Its a privacy concern.
All you can do is, programmatically launching your application's preferences in the Settings App, and again its users wish to disable or enable the access.

Handling next, completed and error in ReactiveCocoa

I am still fairly new in the ReactiveCocoa world and I just wanted to get this common scenario clarified. I noticed that other people are struggling with this matter on GitHub and SO, but I am still missing a proper answer.
The following example does work, but I saw that Justin Summers says that subscriptions-within-subscriptions or subscriptions in general could be code smell. Therefor I want to try and avoid bad habits when learning this new paradigm.
So, the example (using MVVM) is pretty simple:
A ViewController contains a login button which is connected to a login command in the viewmodel
The ViewModel specifies the command action and simulates some network request for this example.
The ViewController subscribes to the command's executingSignals and is able to differentiate the three types of returns: next, error and complete.
And the code.
1 (ViewController):
RAC(self.loginButton, rac_command) = RACObserve(self, viewModel.loginCommand);
2 (ViewModel):
self.loginCommand = [[RACCommand alloc] initWithEnabled:canLoginSignal
signalBlock:^RACSignal *(id input) {
return [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
BOOL success = [username isEqualToString:#"user"] && [password isEqualToString:#"password"];
// Doesn't really make any sense to use sendNext here, but lets include it to test whether we can handle it in our viewmodel or viewcontroller
[subscriber sendNext:#"test"];
if (success)
{
[subscriber sendCompleted];
} else {
[subscriber sendError:nil];
}
// Cannot cancel request
return nil;
}] materialize];
}];
3 (ViewController):
[self.viewModel.loginCommand.executionSignals subscribeNext:^(RACSignal *execution) {
[[execution dematerialize] subscribeNext:^(id value) {
NSLog(#"Value: %#", value);
} error:^(NSError *error) {
NSLog(#"Error: %#", error);
} completed:^{
NSLog(#"Completed");
}];
}];
How would you do this in a more ReactiveCococa-kind-a-way?
With the way RACCommand works, values come from the executionSignals signal, errors from the errors signal, and completions, well, those are where one might use -materialize and -dematerialize as in your example.
In the example given, login, it arguably don't require completion to model it. Instead a login signal could be defined to be binary in behavior: it either sends #YES (for example), or sends an error. Under these conditions, the code would be:
[[self.viewModel.loginCommand.executionSignals concat] subscribeNext:^(id _) {
// Handle successful login
}];
[self.viewModel.loginCommand.errors subscribeNext:^(NSError *error) {
// Handle failed login
}];
This is obviously a bit of a divergence from the typical subscribeNext:error:completed: pattern typical in RAC. That's just due to RACCommand's API.
Note that the -concat operator has been applied to executionSignals in order to surface the inner values and avoid inner subscriptions. You might also see -flatten or -switchToLatest used in other RACCommand examples, but whenever a command has its allowsConcurrentExecution property set to NO (which is the default), then execution happens serially, making -concat the operator that naturally matches and expresses those serial semantics. Applying -flatten or -switchToLatest would actually work, since they degenerate to -concat when applied to serial signal-of-signals, but they express semantics to the reader that don't apply.

Google Play Games not allowing user to join turn-based match

I create a turn-based match and proceed to invite a single opponent as follows:
GPGMultiplayerConfig *config = [[GPGMultiplayerConfig alloc] init];
// We will automatically match with one other player
config.invitedPlayerIds = #[self.opponent.googlePlayID];
config.minAutoMatchingPlayers = 0;
config.maxAutoMatchingPlayers = 0;
[GPGTurnBasedMatch
createMatchWithConfig:config
completionHandler:^(GPGTurnBasedMatch *match, NSError *error) {
if (error) {
completion(NO);
return;
}
}];
After this device places the first move and passes the next turn to my opponent device, my opponent device receives the push notification to join the match. I respond by joining. At this point my self.match.userMatchStatus for this invited device is invited:
[self.match joinWithCompletionHandler:^(NSError *error) {
if (error) {
completion(NO);
return;
}
}];
This doesn't give an error. Upon calling self.match.isMyTurn, I get back YES. A call to self.match.userMatchStatus gives the status of invited; not joined. The documentation (which is incredibly poor, by the way) states that this joinWithCompletionHandler: method:
Joins a turn-based match that the player has been invited to.
Even when adding a dispatch time delay in of 3 seconds after this, to give it a chance, I find that it's still set to invited. Calling further methods, such as takeTurnWithNextParticipantId:data:results:completionHandler:, fails with an entirely undocumented error:
Error Domain=com.google.GooglePlayGames Code=3 "The operation couldn’t
be completed. (com.google.GooglePlayGames error 3.)"
Here's a link to Google's documentation:
https://developers.google.com/games/services/ios/api/interface_g_p_g_turn_based_match
I guess you are passing the player id instead of participant id to takeTurnWithNextParticipantId. The error code 3 (and http response code 400) means that something in passed parameters is invalid, in my case it was the participant id which i had set wrong.

Error using semaphore token to run code after a group of asynchronous web service calls

I am in a situation where I need to call multiple web service requests at a time to call to my server to delete messages on the server and I am having a difficult time trying to figure out the best way to trigger some methods to refresh my data at the completion of these group of web service calls.
From what I have researched using a semaphore counter should work for what I am wanting to do, but I am running into a problem where upon calling dispatch_release() on my semaphore token my application crashes with this -
libdispatch.dylib`_dispatch_semaphore_dispose$VARIANT$mp:
0x3c03ed70: push {r7, lr}
0x3c03ed72: ldr r1, [r0, #40]
0x3c03ed74: mov r7, sp
0x3c03ed76: ldr r2, [r0, #36]
0x3c03ed78: cmp r1, r2
0x3c03ed7a: bge 0x3c03ed7e ; _dispatch_semaphore_dispose$VARIANT$mp + 14
0x3c03ed7c: trap
Everything i've found on this problem points to the semaphore token being referenced by something but I can't see what would have a reference to the token.
- (void)deleteMultipleThreadsForMessages:(NSArray *)messages withCompletion:(void(^)(BOOL allThreadsDeleted))completion
{
NSDictionary *userDictionary = [[MSMKeychainAccess sharedKeychain] returnUserDictionary];
long messagesCount = [messages count] - 1;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(messagesCount);
BOOL (^isLastRequest)(void) = ^BOOL (void) {
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW);
if (0 == result) {
return false;
}
dispatch_release(semaphore);
return true;
};
for (MSMMessageDataModel *message in messages) {
NSDictionary *dictionary = #{#"license": userDictionary[#"license"],
#"messageID" : message.msgID};
NSLog(#"Called this many times! %#", message.msgID);
[[MSMClient sharedClient] soapPostsWithCompletion:^(NSDictionary *response, NSError *error) {
if (error) {
isLastRequest();
completion(NO);
} else {
if (isLastRequest()) {
completion(YES);
}
}
} andRequest:[[[MSMRequestsController alloc] init] createGetMessagesSOAPCallAndSendRequest:#"DeleteThread"
withParameters:dictionary]];
}
}
EDIT
Thanks for the great answers. As Dustin said I was attempting to use dispatch_semaphore for something that it should not be used for. I accepted his answer because it was simple to implement and didn't need any re-structure of what i'm doing currently to send my web services. I now have some good reading material though about dispatch_groups in general though!
Thanks for all your help!
I'm not sure this is the most efficient way to solve your problem, but just looking through your code, one problem you might be running into is that you're creating the semaphore with a count and trying to release it when that count is less than the initial count. In GCD, always create a semaphore with 0 and then signal the semaphore to the correct number of "resources" you need to count. The reason is because semaphores can't be destroyed/released if their count is less than the initial count. It kinda makes sense if you think of it as a resource counter. Having a semaphore number less than the initial count means that you have a worker still using one of the resources.
You can see this code here http://opensource.apple.com/source/libdispatch/libdispatch-187.7/src/semaphore.c. The code that will throw the exception in _dispatch_semaphore_dispose is:
if (dsema->dsema_value < dsema->dsema_orig) {
DISPATCH_CLIENT_CRASH(
"Semaphore/group object deallocated while in use");
}
This is exactly what dispatch_group is designed to address. You dispatch several blocks to a group, and when they have all completed, another block will be executed (or you can wait on them if you need a synchronous behavior).
First see Waiting on Groups of Queued Tasks in the Concurrency Programming Guide, and see the dispatch_group functions in the GCD reference. To see them in action, see the JuliaCell example from Chapter 13 of iOS:PTL. Cocoa Samurai also has some examples.
Even if you can't actually dispatch the blocks to a group (it may not work with how MSMClient operates), you can still use dispatch groups manually by calling dispatch_group_enter() and dispatch_group_leave() to get the same behavior you're trying to get from the semaphore.
As a side note, BOOL is not the same as bool. BOOL return YES and NO, which are different than true and false (which I assume means you're compiling this as ObjC++, which always makes me shudder, but that's a different issue). Mixing them can matter because they can be (and sometimes are) different sizes. I've had to crashes due to that personally.
This is an incorrect usage for dispatch_semaphore -- it isn't meant to do what you are attempting. What you need is a counting variable that is thread safe. You can get this using __sync_sub_and_fetch.
- (void)deleteMultipleThreadsForMessages:(NSArray *)messages withCompletion:(void(^)(BOOL allThreadsDeleted))completion
{
NSDictionary *userDictionary = [[MSMKeychainAccess sharedKeychain] returnUserDictionary];
__block long messagesCount = [messages count];
for (MSMMessageDataModel *message in messages) {
NSDictionary *dictionary = #{#"license": userDictionary[#"license"],
#"messageID" : message.msgID};
NSLog(#"Called this many times! %#", message.msgID);
[[MSMClient sharedClient] soapPostsWithCompletion:^(NSDictionary *response, NSError *error) {
long which = __sync_sub_and_fetch(&messageCount, 1);
if(which == 0)
completion(error == nil);
} andRequest:[[[MSMRequestsController alloc] init] createGetMessagesSOAPCallAndSendRequest:#"DeleteThread"
withParameters:dictionary]];
}
}
__sync_sub_and_fetch tells the CPU that you want it to take latest version of 'messageCount' from all threads (and cores), subtract 1, and give you the result.

Resources