I am working on a turn based iOS game and trying to populate my list of games the player is participating in.
for (unsigned i = 0; i < [matches count]; i++)
{
// Only load data for games in progress.
// NOTE: Might want to handle finished games later.
if ([matches[i] status] != GKTurnBasedMatchStatusEnded)
{
// Send off another block to retrieve the match's data.
[(GKTurnBasedMatch*)matches[i] loadMatchDataWithCompletionHandler: ^(NSData *matchData, NSError *error)
{
// Prepare the game.
Game* game;
if (matchData.length == 0)
{
// If the match data is empty, this is a new game. Init from scratch.
game = [[Game alloc] init];
}
else
{
// Otherwise, unpack the data and init from it.
game = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
}
game.match = matches[i];
// Load the displayNames for the players.
bool lastIndex = i == ([matches count] - 1);
[self loadPlayerIdentifiersForGame:game intoArray:blockGames lastIndex:lastIndex];
}];
}
}
Unfortunately, I am having an issue where I can't tag each block with its index. That is, i is always 0 by the time the block executes. Is there a way that I can make sure the block knows what i WAS at the time it was launched?
I sidestepped the issue where my UITableView wouldn't reload if the last game was over by doing this instead:
GKTurnBasedMatch* match;
for (int j = ([matches count] - 1); j >= 0; j --)
{
match = matches[j];
if (match.status != GKTurnBasedMatchStatusEnded)
break;
}
bool lastIndex = (matches[i] == match);
[self loadPlayerIdentifiersForGame:game intoArray:blockGames lastIndex:lastIndex];
-(void)someMethod {
...
for (unsigned i = 0; i < [matches count]; i++)
{
// Only load data for games in progress.
// NOTE: Might want to handle finished games later.
if ([matches[i] status] != GKTurnBasedMatchStatusEnded)
[self loadMatch:i of:matches];
}
}
-(void) loadMatch:(int)i of:(NSArray *)matches {
// Send off another block to retrieve the match's data.
[(GKTurnBasedMatch*)matches[i] loadMatchDataWithCompletionHandler: ^(NSData *matchData, NSError *error)
{
// Prepare the game.
Game* game;
if (matchData.length == 0)
{
// If the match data is empty, this is a new game. Init from scratch.
game = [[Game alloc] init];
}
else
{
// Otherwise, unpack the data and init from it.
game = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
}
game.match = matches[i];
// Load the displayNames for the players.
bool lastIndex = i == ([matches count] - 1);
[self loadPlayerIdentifiersForGame:game intoArray:blockGames lastIndex:lastIndex];
}];
}
the simplest way is to pass a third parameter containing the tag..
And I suggest to use typedef.. to better write code (and let autocompletion work for you..)
typedef void (^CompletionBlock)(NSInteger tag, NSData * data, NSError *err);
and use CompletionBlock when defining loadMatchDataWithCompletionHandler.
Related
I’m trying to make a custom matchmakingview using a matchmaker. The code below is used to find a match.
When i run this on two different devices with different Game Center accounts, both will get a match but none will connect to the match. They will just get stuck in the while loop in infinity and never get out. Have i missed something, do you need to call something to actually connect to the match?
- (void) findMatch{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = nil;
NSLog(#"Start searching!");
[matchmaker findMatchForRequest:request
withCompletionHandler:^(GKMatch *match, NSError *error)
{
if (error) {
// Print the error
NSLog(#"%#", error.localizedDescription);
}
else if (match != nil)
{
curMatch = match;
curMatch.delegate = self;
NSLog(#"Expected: %i", match.expectedPlayerCount);
while (match.expectedPlayerCount != 0){
NSLog(#"PLayers: %i", curMatch.playerIDs.count);
}
NSLog(#"Start match!");
}
}];
You should not be using a while loop to wait for expectedPlayerCount to reach 0, instead implement the GKMatchDelegate method:
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (!self.matchStarted && match.expectedPlayerCount == 0) {
self.matchStarted = YES;
//Now you should start your match.
}
}
I am trying to pull the turn based games in which a player is participating in order to populate my tableView.
This is my function to pull their games:
- (void) loadMatchDataWithArray:(NSMutableArray*)currentGames Flag:(bool*)returned
{
NSMutableArray* __block blockGames = currentGames;
bool* __block blockReturn = returned;
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
if (matches)
{
for (int i = 0; i < matches.count; i++)
{
[(GKTurnBasedMatch*)matches[i] loadMatchDataWithCompletionHandler: ^(NSData *matchData, NSError *error)
{
int size = [matchData length];
if (size != 0)
{
Game* game = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
[blockGames addObject:game];
}
else
{
Game* game = [[Game alloc] init];
[blockGames addObject:game];
game.activePlayer = [GKLocalPlayer localPlayer];
}
*blockReturn = true;
}];
}
}
else
{
*blockReturn = true;
}
}];
}
And this is where I call it:
- (void)viewDidLoad
{
[super viewDidLoad];
[[self tableView]
setBackgroundView:[[UIImageView alloc]
initWithImage:[UIImage imageNamed:#"iPhoneBackground-568h"]]];
bool* returned = false;
[[GKMatchHelper sharedInstance] loadMatchDataWithArray:currentGames Flag:returned];
while (!returned);
[self.tableView reloadData];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
Sadly, this is just giving me a blank black screen and never returns. Is there a way that I can detect when my block comes back and display a loading spinner until then, at which point I would reload the table?
EDIT:
I have revised my code and brought the function inside my MainMenuViewController, and now it builds but never displays the data.
- (void) loadMatchData
{
NSMutableArray* __block blockGames = currentGames;
MainMenuViewController* __weakSelf = self;
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
if (matches)
{
for (int i = 0; i < matches.count; i++)
{
[(GKTurnBasedMatch*)matches[i] loadMatchDataWithCompletionHandler: ^(NSData *matchData, NSError *error)
{
int size = [matchData length];
if (size != 0)
{
Game* game = [NSKeyedUnarchiver unarchiveObjectWithData:matchData];
[blockGames addObject:game];
}
else
{
Game* game = [[Game alloc] init];
[blockGames addObject:game];
game.activePlayer = [GKLocalPlayer localPlayer];
}
[__weakSelf.tableView reloadData];
}];
}
}
[__weakSelf.tableView reloadData];
}];
[__weakSelf.tableView reloadData];
}
And now in my ViewDidLoad I just call:
[self loadMatchData];
Oh dear. Do NOT halt the program execution with "while" loops!
Why not simply call [self.tableView reloadData] at the end of your block?
So,
Remove the last 2 lines in the viewDidLoad method
Replace *blockReturn = true; with [self.tableView reloadData] (you might need to keep a weak reference to 'self' to avoid retain cycles)
Never ever use while (this and that) to wait for an operation to complete. A non-responsive UI is bad and it will cause the users to abandon your app.
I am using the MKNetworkKit library to make REST calls to a server. This particular snippet sets is called when a user wants to make a photo favorite or not favorite. It is pretty straight forward objective-c block code if we are only setting favorite flag for a single photo. However I have impelmented a multiselect mechanism in the GUI so that a user can select several photos and favorite them all at once.
The function I wrote to do this works just fine, but it just doesn't feel very clean to me.
What I dislike:
Tracking all of the callbacks with a block counter. I am wondering if there is a more elegant way to handle this.
The same code exists in both completion blocks. However that is just how MKNetworkKit is used (one block for success, one for error). I suppose if I made this an instance method, I could handle it by calling another i-method, but that seems just as messy to do all the setup. I'd like to keep this as a handy class method utility.
Suggestions?
+(BOOL)updateAssets:(NSArray*)assets
isFavorite:(BOOL)isFavorite
completion:(MyAssetCompletion)completion // (BOOL success)
{
assert(assets);
if (assets == nil || assets.count == 0) return NO;
__block BOOL bError = NO;
__block NSInteger counter = 0; // Use a counter to track number of completed REST calls
for(MyAsset *asset in assets){
MyUpdateAssetForm *form = [[MyUpdateAssetForm alloc] init];
form.isPrivate = isFavorite ? #(1) : #(0);
[[MyRESTEngine sharedInstance] updateAssetWithUUID:asset.UUID
withForm:form
completionBlock:^{
counter++;
if(counter == assets.count){
completion(bError == NO);
}
} errorBlock:^(NSError *error, NSString *additionalInfo) {
bError = bError || YES;
counter++;
if(counter == assets.count){
completion(bError == NO);
}
}];
}
return YES;
}
You can try this (untested):
(By the way, your current implementation is not thread safe as the counter might be incremented from more then one thread in a non-atomic manner)
#see NSConditionLock
- (BOOL) updateAssets:(NSArray*)assets
isFavorite:(BOOL)isFavorite
completion:(bool_block_t)completion
{
assert(assets && [assets count]);
__block NSMutableArray* failed = [NSMutableArray new];
__block NSConditionLock* lock = [[NSConditionLock alloc] initWithCondition:[assets count]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
[lock lockWhenCondition:0];
completion([failed count] == 0);
});
NSNumber* val = (isFavorite ? #1 : #0);
for (MyAsset* asset in assets) {
MyUpdateAssetForm* form = [[MyUpdateAssetForm alloc] init];
form.isPrivate = val;
[self updateAssetWithUUID:asset.UUID
withForm:form
completionBlock:^{[lock unlockWithCondition:[lock condition] - 1];}
errorBlock:^(NSError *error, NSString *additionalInfo)
{
[lock lock];
[failed addObject:asset];
[lock unlockWithCondition:[lock condition] - 1];
}];
}
return YES;
}
I'm trying to implement a real-time multiplayer game with a custom UI (no GKMatchMakerViewController). I'm using startBrowsingForNearbyPlayersWithReachableHandler:
^(NSString *playerID, BOOL reachable) to find a local player, and then initiating a match request with the GKMatchmaker singleton (which I have already initiated).
Here's where I'm having trouble. When I send a request, the completion handler fires almost immediately, without an error, and the match it returns has an expected player count of zero. Meanwhile, the other player definitely has not responded to the request.
Relevant code:
- (void) findMatch
{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = NUM_PLAYERS_PER_MATCH; //2
request.maxPlayers = NUM_PLAYERS_PER_MATCH; //2
if (nil != self.playersToInvite)
{
// we always successfully get in this if-statement
request.playersToInvite = self.playersToInvite;
request.inviteeResponseHandler = ^(NSString *playerID, GKInviteeResponse
response)
{
[self.delegate updateUIForPlayer: playerID accepted: (response ==
GKInviteeResponseAccepted)];
};
}
request.inviteMessage = #"Let's Play!";
[self.matchmaker findMatchForRequest:request
withCompletionHandler:^(GKMatch *match, NSError *error) {
if (error) {
// Print the error
NSLog(#"%#", error.localizedDescription);
}
else if (match != nil)
{
self.currentMatch = match;
self.currentMatch.delegate = self;
// All players are connected
if (match.expectedPlayerCount == 0)
{
// start match
[self startMatch];
}
[self stopLookingForPlayers];
}
}];
}
Figured it out! I needed to call - (void)matchForInvite:(GKInvite *)invite completionHandler:(void (^)(GKMatch *match, NSError *error))completionHandler in my invitation handler so that both players have the same match data.
I want to compare an array containing int values to a number
example [1,2,30,1,40,200,10,500] < 500 meaning if array contains elements less than 500.
How can i do dis?
Thank alot in advance
I did this bt it say invalid opernd:
if ([placeName count])
{
for (int i =0; i < [placeName count]; i++)
{
NSArray *sortedArray=[placeName sortedArrayUsingFunction:intSort context:NULL];
self.placeName = [NSMutableArray arrayWithArray:sortedArray];
NSMutableArray *tempArray = [sortedArray objectAtIndex:i];
NSDecimalNumber *Distance = [tempArray objectForKey:#"distance"];
NSMutableArray *intDistanse=[Distance intValue];
DLog(#"Distance%d", intDistanse);
NSMutableArray *intDistanse=[Distance intValue];
DLog(#"Distance%d", intDistanse);
for(int j = 0; j < [intDistanse count]; j++)
{
if ( intDistanse[j] < 500 ) //here its says error
{
DLog(#"success");
}
}
}
}
What kind of an array? NSArray or language array?
Easiest way is a loop. Without knowing the type of the array, this is pseudo code.
for(int i = 0; i < arrayCount; i++)
if ( array[i] < 500 )
.... got an element less than 500 ....
The code doesn't really make sense:
that code should be generating a ton of compiler warnings; every warning indicates a bug that should be fixed. Also, try "Build and Analyze".
you are sorting an array every time through the loop; waste of resources and doesn't make sense
everything being typed as NSMutableArray* doesn't make sense; do you really have an array of arrays of arrays?
... calling objectForKey: on anything but an NSDictionary doesn't work
intValue returns an (int), not an NSMutableArray*
if intDistance is an (int), then it should just be ( intDistance < 500 )
Overall, I would suggest you step back and review the Objective-C language guide and a bunch of working examples.
Typically, you loop over each element in the array, checking if each element is less than 500.
For example:
int i;
for (i=0; i < THE_SIZE_OF_THE_ARRAY; ++i)
{
if (array[i] < 500)
{
/* Do something */
}
}
If you want to see whether there is at least one item that matches your condition:
bool found = false;
for(int i = 0; i < sizeof(example)/sizeof(example[0]); ++i)
{
if(example[i] < 500)
{
found = true;
break;
}
}
And if you want to check whether all items match your condition:
bool found = true;
for(int i = 0; i < sizeof(example)/sizeof(example[0]); ++i)
{
if(example[i] >= 500)
{
found = false;
break;
}
}
Did find a solution to it type casting:
NSMutableArray *tempArray = [sortedArray objectAtIndex:i];
//DLog(#"sortedArray%#", sortedArray);8=
NSNumber *DistanceNum = [tempArray objectForKey:#"distance"];
NSLog(#"distance%#:::",DistanceNum);
NSInteger intDistance = (int)[DistanceNum floatValue];
if(intDistance<500)
{
NSLog(#"h:)");
NSString *notifications =#"Yes";
[[AppHelper mDataManager] setObject:notifications forKey:#"notifications"];
NSLog(#"notifications:%#",notifications);
RemindMeViewController *object = [[RemindMeViewController alloc] initWithNibName:#"RemindMeViewController" bundle:nil];
// RemindMeViewController *object=[[RemindMeViewController alloc]initWithNibName];
NSLog(#"notifications set");
[object scheduleNotification];
}
You can do this very easily by just implementing the observer to compare. Following is just an example to implement the custom observer to compare
#implementation NSArray (KVO)
- (void)addObserver:(NSObject *)observer toObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
NSUInteger idx=[indexes firstIndex];
while(idx!=NSNotFound)
{
[[self objectAtIndex:idx] addObserver:observer
forKeyPath:keyPath
options:options
context:context];
idx=[indexes indexGreaterThanIndex:idx];
}
}
- (void)removeObserver:(NSObject *)observer fromObjectsAtIndexes:(NSIndexSet *)indexes forKeyPath:(NSString *)keyPath
{
NSUInteger idx=[indexes firstIndex];
while(idx!=NSNotFound)
{
[[self objectAtIndex:idx] removeObserver:observer
forKeyPath:keyPath];
idx=[indexes indexGreaterThanIndex:idx];
}
}
-(void)addObserver:(id)observer forKeyPath:(NSString*)keyPath options:(NSKeyValueObservingOptions)options context:(void*)context;
{
if([isa instanceMethodForSelector:_cmd]==[NSArray instanceMethodForSelector:_cmd])
NSRaiseException(NSInvalidArgumentException,self,_cmd,#"not supported for key path %# (observer was %#)", keyPath, observer);
else
[super addObserver:observer
forKeyPath:keyPath
options:options
context:context];
}
-(void)removeObserver:(id)observer forKeyPath:(NSString*)keyPath;
{
if([isa instanceMethodForSelector:_cmd]==[NSArray instanceMethodForSelector:_cmd])
NSRaiseException(NSInvalidArgumentException,self,_cmd,#"not supported for key path %# (observer was %#)", keyPath, observer);
else
[super removeObserver:observer
forKeyPath:keyPath];
}
#end