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

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.

Related

How to remove dialog from dialog list Using QMServicesManager in case of Group and Public chat?

This question is extension to my previous question with new requirements. This is My Previous question.
So My New Requirement is :
Now If I want to delete group chat how I should handle this ? If I use the same method inside it we are passing forAllUsers as "NO" which is hard coded. written inside QMChatServices.m
- (void)deleteDialogWithID:(NSString *)dialogId completion:(void (^)(QBResponse *))completion {
NSParameterAssert(dialogId);
__weak __typeof(self)weakSelf = self;
[QBRequest deleteDialogsWithIDs:[NSSet setWithObject:dialogId] forAllUsers:NO successBlock:^(QBResponse *response, NSArray *deletedObjectsIDs, NSArray *notFoundObjectsIDs, NSArray *wrongPermissionsObjectsIDs) {
//
[weakSelf.dialogsMemoryStorage deleteChatDialogWithID:dialogId];
[weakSelf.messagesMemoryStorage deleteMessagesWithDialogID:dialogId];
if ([weakSelf.multicastDelegate respondsToSelector:#selector(chatService:didDeleteChatDialogWithIDFromMemoryStorage:)]) {
[weakSelf.multicastDelegate chatService:weakSelf didDeleteChatDialogWithIDFromMemoryStorage:dialogId];
}
[weakSelf.loadedAllMessages removeObjectsForKeys:deletedObjectsIDs];
if (completion) {
completion(response);
}
} errorBlock:^(QBResponse *response) {
//
if (response.status == QBResponseStatusCodeNotFound || response.status == 403) {
[weakSelf.dialogsMemoryStorage deleteChatDialogWithID:dialogId];
if ([weakSelf.multicastDelegate respondsToSelector:#selector(chatService:didDeleteChatDialogWithIDFromMemoryStorage:)]) {
[weakSelf.multicastDelegate chatService:weakSelf didDeleteChatDialogWithIDFromMemoryStorage:dialogId];
}
}
else {
[weakSelf.serviceManager handleErrorResponse:response];
}
if (completion) {
completion(response);
}
}];
}
So Now my doubt is..
Question 1 : What if we want to delete dialog for all the users. Question 2 : Lets say there are 3 users. User1 , User2 and User3. Now User1 has created group with User2 and User3.
So how this method is useful for all the different 3 users. I mean What happens if User1 uses
[ServicesManager.instance.chatService deleteDialogWithID:dialog.ID completion:nil];
and what happens if User2 and User3 uses the same method.
Weather it works as exit from the dialog or deleting dialog. I'm little confused how this method works for different users in case of group and public chat.
Question 3 : Is there any other way to exit from the group chat ? I hope it is clear !!
You right, there is no interface for deleting dialog for all users right now. But keep in mind that only the owner of group dialog (its creator) can delete it. And owner cannot be changed, even if he will left the dialog. We will consider adding such method in a near future, but for now you can easily modify it for your needs (you are welcome to fork from our services repository).
If any of the user uses this method - he will be out of that group (by server), but group itself will still exist with other members. But other members will only know about left users when they will redownload dialog from REST. So in order to notify them live, we are sending XMPP notifications before leaving, like this one
Pretty much what I said in 2. Notifying users live about our leaving and deleting dialog through REST (QBRequest) request.

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];

m7/m8 detect if motion activity authorization was denied

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.

Game Center leaderboard score request's completion handler is never called for players that have no score

My game implements a custom user interface that lists the local player's friends.
I also have a Game Center leaderboard.
When my game lists the players, it also tries to load their scores from the leaderboard, using this code:
GKLeaderboard *request = [[GKLeaderboard alloc] initWithPlayerIDs:myFriends];
request.timeScope = GKLeaderboardTimeScopeAllTime;
request.identifier = #"my_leaderboards";
if (request != nil) {
[request loadScoresWithCompletionHandler: ^(NSArray *scores, NSError *error) {
if (error != nil) {
NSLog(#"Error: %#",error.localizedDescription);
}
if (scores != nil) {
NSLog(#"WORKED: %#",scores);
}
}];
}
And it works just fine.
... except when one of the friends has no score (for instance, they never played the game in the first place). When one of the players in myFriends has no score entry in the leaderboard, the completion handler is never called. There is no error and no score reported, because it never fires in the first place.
I realised this when testing an account that has two friends. One friend has played the game (so they have a score), and the other has not. The completion handler never got called. Then, I unfriended the guy that had no score, and the completion handler worked fine, returning the score of the friend that did have a score.
I somewhat understand this behaviour - after all, I'm asking it to give me a score that does not exist. But is there a workaround? As in, tell it to return a 0 if there is no score?
iOS 7.
It cannot be helped, the completion handler indeed won't be called because there is no score entry in the leaderboards. Although I still don't understand why doesn't it just return an error saying so.
Since the custom friend list just shows statistics, I just put a loading icon in place for each statistic. If the handler is not called for more than 10 seconds, I assume that there is no score entry and just display a 0.

GameKit programmatic matching flow

I'm having the devils own time with Game Kit programmatic matching, and can only assume that despite reading all the tutorials I can lay my hands on... I've got the flow wrong somewhere.
Sign in to the Sandbox works fine, the app is being distributed with an App ID specific profile that is Game Center enabled. It all seems to work fine except that it never finds another "nearby" player.
The code below is my authentication handler, which is called correctly, but as I say despite it logging "Starting browser for nearby players", none are ever reported, blue tooth on same wifi network etc etc etc. Utterly perplexed as to what I am doing wrong.
-(void) authenticationHandler{
if ([_currentScene conformsToProtocol:#protocol(BOMScene)]){
MyScene<BOMScene> *theScene = (MyScene<BOMScene> *) _currentScene;
//Make sure the current scene gets the message that they are now authenticated
if ([GKLocalPlayer localPlayer].isAuthenticated){
[theScene localPlayerAuthenticated];
} else {
[theScene localPlayerDeauthenticated];
}
}
NSLog(#"Game Center Status Change: %#", _localPlayer.authenticated ? #"Available" : #"Not Available");
if (_localPlayer.authenticated){
if (!_matchMaker){
_matchMaker= [GKMatchmaker sharedMatchmaker];
NSLog(#"Starting to browser for nearby players");
[_matchMaker startBrowsingForNearbyPlayersWithReachableHandler:^(NSString *playerID, BOOL reachable) {
NSLog(#"Nearby player %# is %#",playerID, reachable ? #"available" : #"no longer available");
if (reachable){
[_nearbyPlayers addObject:playerID];
} else {
[_nearbyPlayers removeObject:playerID];
}
}];
}
} else {
_matchRequest = nil;
[_matchMaker stopBrowsingForNearbyPlayers];
[_nearbyPlayers removeAllObjects];
_matchMaker = nil;
}
You need to install an invitation handler.
Please see my answer here for a full breakdown:
Some startBrowsingForNearbyPlayersWithReachableHandler questions

Resources