if network not available then it showing alert of "Network not available" but after that loading spinner is not able to dismiss... It continuously showing loading spinner.....
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self showSpinner];
[self getDashBoard];
}
- (void) getDashBoard {
//[API call]
// if success then
//assign values from response
[self dismissSpinner]
// else
// [self dismissSpinner]
// [Utility displayAlert:#"Error" WithMessage:#"Network not available"];
}
#pragma mark - LodingSpinner
- (void) showSpinner {
if (([[[UIDevice currentDevice] systemVersion] compare:#"8.0" options:NSNumericSearch] == NSOrderedAscending)) {
// use UIAlertView
}
else {
lodingSpinner = [Utility showProgress];
[self.parentViewController presentViewController:lodingSpinner animated:NO completion:nil];
}
}
- (void) dismissSpinner {
if (([[[UIDevice currentDevice] systemVersion] compare:#"8.0" options:NSNumericSearch] == NSOrderedAscending)) {
// use UIAlertView
}
else {
[lodingSpinner dismissViewControllerAnimated:YES completion:nil];
}
}
Related
3D Touch is introduced in iPhone 6s/iPhone 6s plus,and I am wondering if we could have access to 3D Touch related API and do it programmatically in our app.Does anyone has some ideas?
You will have access to 3D touch functionality in apps. There will be a new property, force, in the UITouch class that will hold the strength that a tap event has. 1.0 is defined to be an "average touch".
Source: https://developer.apple.com/ios/3d-touch/
I am sharing the source code for pressing on app icon populate 4 item in list
Step 1:- Import class in appDelegate.m
import sys/utsname.h
Step 2:-
#pragma MARK for Get Machine Name
- (NSString *) machineName {
struct utsname systemInfo;
uname(&systemInfo);
return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if([[self machineName] isEqualToString:#"iPhone8,2"]|| [[self machineName] isEqualToString:#"iPhone8,1"]) {
[self addEventsFor3DTouchEvents];
}
return YES;
}
#pragma MARK for Adding Action for Three D Touch Eventes
- (void) addEventsFor3DTouchEvents {
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
UIApplicationShortcutItem *item1 = [[UIApplicationShortcutItem alloc] initWithType:#"dynamic1" localizedTitle:TITLE_NAME_1 localizedSubtitle:#"" icon:[UIApplicationShortcutIcon iconWithTemplateImageName:TITLE_IMAGE_NAME_1] userInfo:nil];
UIApplicationShortcutItem *item2 = [[UIApplicationShortcutItem alloc] initWithType:#"dynamic1" localizedTitle:TITLE_NAME_2 localizedSubtitle:#"" icon:[UIApplicationShortcutIcon iconWithTemplateImageName:TITLE_IMAGE_NAME_2] userInfo:nil];
UIApplicationShortcutItem *item3 = [[UIApplicationShortcutItem alloc] initWithType:#"dynamic1" localizedTitle:TITLE_NAME_3 localizedSubtitle:#"" icon:[UIApplicationShortcutIcon iconWithTemplateImageName:TITLE_IMAGE_NAME_3] userInfo:nil];
UIApplicationShortcutItem *item4 = [[UIApplicationShortcutItem alloc] initWithType:#"dynamic1" localizedTitle:TITLE_NAME_4 localizedSubtitle:#"" icon:[UIApplicationShortcutIcon iconWithTemplateImageName:TITLE_IMAGE_NAME_4] userInfo:nil];
[[UIApplication sharedApplication] setShortcutItems: #[ item1, item2, item3, item4 ]];
}
}
#pragma mark - 3DTouch Delegate Methods
- (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler {
[self moveThrough3DTouch:shortcutItem];
}
#pragma MARK for Handling Action for Three D Touch Events
- (void)moveThrough3DTouch:(UIApplicationShortcutItem *)temp {
if ([temp.localizedTitle isEqualToString:TITLE_NAME_1]) {
[self.tabBarController setSelectedIndex:0];
} else if([temp.localizedTitle isEqualToString:TITLE_NAME_2]) {
[self.tabBarController setSelectedIndex:1];
} else if([temp.localizedTitle isEqualToString:TITLE_NAME_3]) {
[self.tabBarController setSelectedIndex:2];
} else if([temp.localizedTitle isEqualToString:TITLE_NAME_4]) {
[self.tabBarController setSelectedIndex:3];
}
}
I am using Tab Bar controller in my app, If want to move in view controlled:
- (void) moveToControllerScene {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:STORY_BOARD_IDENTIFIER bundle:nil];
YOUR_CONTROLLER_OBJECT *obj = [storyboard instantiateViewControllerWithIdentifier:#"YOUR_CONTROLLER_OBJECT"];
[navController pushViewController:obj animated:YES];
}
In GCHelper.h
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#interface abGCHelper : NSObject {
BOOL gameCenterAvailable;
BOOL userAuthenticated;
}
#property (assign, readonly) BOOL gameCenterAvailable;
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier;
#end
In GCHelper.m
#import "abGCHelper.h"
#implementation abGCHelper
#synthesize gameCenterAvailable;
static abGCHelper *_sharedHelper = nil;
-(abGCHelper*)defaultHelper {
// dispatch_once will ensure that the method is only called once (thread-safe)
static dispatch_once_t pred = 0;
dispatch_once(&pred, ^{
_sharedHelper = [[abGCHelper alloc] init];
});
return _sharedHelper;
}
- (BOOL)isGameCenterAvailable {
// check for presence of GKLocalPlayer API
Class gcClass = (NSClassFromString(#"GKLocalPlayer"));
// check if the device is running iOS 4.1 or later
NSString *reqSysVer = #"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer
options:NSNumericSearch] != NSOrderedAscending);
return (gcClass && osVersionSupported);
}
- (id)init {
if ((self = [super init])) {
gameCenterAvailable = [self isGameCenterAvailable];
if (gameCenterAvailable) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(authenticationChanged) name:GKPlayerAuthenticationDidChangeNotificationName object:nil];
}
}
return self;
}
- (void)authenticateLocalUserOnViewController:(UIViewController*)viewController
setCallbackObject:(id)obj
withPauseSelector:(SEL)selector
{
if (!gameCenterAvailable) return;
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
NSLog(#"Authenticating local user...");
if (localPlayer.authenticated == NO) {
[localPlayer setAuthenticateHandler:^(UIViewController* authViewController, NSError *error) {
if (authViewController != nil) {
if (obj) {
[obj performSelector:selector withObject:nil afterDelay:0];
}
[viewController presentViewController:authViewController animated:YES completion:^ {
}];
} else if (error != nil) {
// process error
}
}];
}
else {
NSLog(#"Already authenticated!");
}
}
-(void)authenticationChanged {
if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {
NSLog(#"Authentication changed: player authenticated.");
userAuthenticated = TRUE;
// Load the leaderboard info
// Load the achievements
} else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {
NSLog(#"Authentication changed: player not authenticated.");
userAuthenticated = FALSE;
}
}
-(void)reportScore:(int64_t)score forLeaderboardID:(NSString*)identifier
{
GKScore *scoreReporter = [[GKScore alloc] initWithLeaderboardIdentifier: identifier];
scoreReporter.value = score;
scoreReporter.context = 0;
[GKScore reportScores:#[scoreReporter] withCompletionHandler:^(NSError *error) {
if (error == nil) {
NSLog(#"Score reported successfully!");
} else {
NSLog(#"Unable to report score!");
}
}];
}
I referred to online tutorials to do this. Now, the problem is:
How do I submit the highScore defined in MyScene.m ?
Where do I have to put my Leaderboard ID?
Please help
Thanks...
I have a view controller and i have added UIImagePicker as subview. On changing device orientation from portrait to landscape, camera appears to be on half screen rather than full screen. the images in the camera is also rotated 90 degrees. I am supporting device orientation in my app. how to fix this camera orientation issue. Any help is appreciated.
//Code for presenting camera
- (id)init
{
self = [super init];
if (self) {
UIImagePickerController * picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypeCamera ;
picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
picker.allowsEditing = YES;
self.cameraPicker = picker;
cameraPicker.view.frame = CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height );;
//cameraPicker.view.frame = CGRectMake(0,0,[UIScreen mainScreen].bounds.size.width,[UIScreen mainScreen].bounds.size.height );
[self.view addSubview:cameraPicker.view];
}
return self;
}
//Code for supporting device orientation
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
[self setFramesForControls:toInterfaceOrientation];
}
-(void)setFramesForControls :(UIInterfaceOrientation)orientation
{
if (orientation == UIInterfaceOrientationPortrait || orientation == UIInterfaceOrientationPortraitUpsideDown)
{
cameraPicker.view.frame = CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height );
}
else if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight)
{
cameraPicker.view.frame = CGRectMake(0,0,self.view.frame.size.width,self.view.frame.size.height );
}
}
Try this its working fine
declare this in viewdidload method
picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.sourceType = UIImagePickerControllerSourceTypeCamera ;
picker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
picker.allowsEditing = YES;
and on button click event do the following
[self presentModalViewController:picker animated:YES];
no matter to check device orientation;
To prevent your image this code will help you-
Define these macros on the top of your ViewController class-
#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
Then manage your delegate method in this way-
#pragma mark- Delegate methods of UIImagePickerController Class
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *pickedImage = [info objectForKey:UIImagePickerControllerOriginalImage];
NSString *imgType = [info objectForKey:UIImagePickerControllerReferenceURL];
//NSLog(#"%#",imgType);
imgType = [imgType lastPathComponent];
NSData *imageData;
if (SYSTEM_VERSION_LESS_THAN(#"6.0"))
{
// code here
imageData = UIImagePNGRepresentation(pickedImage);
}
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(#"6.0"))
{
// code here
imageData = UIImageJPEGRepresentation(pickedImage, 0.5);
}
NSError * error = nil;
[imageData writeToFile:path options:NSDataWritingAtomic error:&error];
if (error != nil)
{
NSLog(#"ERROR: %#", error);
return;
}
[picker dismissViewControllerAnimated:YES completion: nil];
}
It will capture the images according to your requirement.
After integrating iAd, our app crashes whenever there is an orientation change (even though we lock the orientation to only portrait). We're on PhoneGap 3.2. Here is the output in Xcode:
2013-12-01 13:22:40.906 Wopple[4600:60b] -[CDViAd deviceOrientationChange:]: unrecognized selector sent to instance 0x166b07e0
2013-12-01 13:22:40.907 Wopple[4600:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CDViAd deviceOrientationChange:]: unrecognized selector sent to instance 0x166b07e0'
*** First throw call stack:
(0x2df75e83 0x382d66c7 0x2df797b7 0x2df780af 0x2dec6dc8 0x2df37e71 0x2deabab1 0x2e891ec5 0x306ffca5 0x306ff2e9 0x306fecfd 0x30764321 0x32bde76d 0x32bde357 0x2df40777 0x2df40713 0x2df3eedf 0x2dea9471 0x2dea9253 0x32bdd2eb 0x3075e845 0xe1e63 0x387cfab7)
libc++abi.dylib: terminating with uncaught exception of type NSException
Here is our iAd code:
//
// CDViAd.m
// Ad Plugin for PhoneGap
//
// Created by shazron on 10-07-12.
// Copyright 2010 Shazron Abdullah. All rights reserved.
// Cordova v1.5.0 Support added 2012 #RandyMcMillan
// Cordova v3.0.0 Support added 2013 #LimingXie
#import "CDViAd.h"
#import <Cordova/CDVDebug.h>
#interface CDViAd()
- (void) __prepare:(BOOL)atTop;
- (void) __showAd:(BOOL)show;
#end
#implementation CDViAd
#synthesize adView;
#synthesize bannerIsVisible, bannerIsInitialized, bannerAtTop, isLandscape;
#pragma mark -
#pragma mark Public Methods
- (CDVPlugin *)initWithWebView:(UIWebView *)theWebView {
self = (CDViAd *)[super initWithWebView:theWebView];
if (self) {
// These notifications are required for re-placing the ad on orientation
// changes. Start listening for notifications here since we need to
// translate the Smart Banner constants according to the orientation.
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(deviceOrientationChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
return self;
}
- (void) createBannerView:(CDVInvokedUrlCommand *)command
{
CDVPluginResult *pluginResult;
NSString *callbackId = command.callbackId;
NSArray* arguments = command.arguments;
NSUInteger argc = [arguments count];
if (argc > 1) {
return;
}
BOOL atTop = NO;
NSString* atTopValue = [arguments objectAtIndex:0];
if( atTopValue ) atTop = [atTopValue boolValue];
[self __prepare:atTop];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
- (void) showAd:(CDVInvokedUrlCommand *)command
{
CDVPluginResult *pluginResult;
NSString *callbackId = command.callbackId;
NSArray* arguments = command.arguments;
NSUInteger argc = [arguments count];
if (argc > 1) {
return;
}
BOOL show = YES;
NSString* showValue = [arguments objectAtIndex:0];
if( showValue ) show = [showValue boolValue];
[self __showAd:show];
pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
Class adBannerViewClass = NSClassFromString(#"ADBannerView");
if (adBannerViewClass && self.adView) {
if( UIInterfaceOrientationIsLandscape( toInterfaceOrientation ) ) {
self.isLandscape = YES;
self.adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
} else {
self.adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
}
[self resizeViews];
}
}
- (void) resizeViews
{
Class adBannerViewClass = NSClassFromString(#"ADBannerView");
if (adBannerViewClass && self.adView) {
CGRect webViewFrame = [super webView].frame;
CGRect superViewFrame = [[super webView] superview].frame;
CGRect adViewFrame = self.adView.frame;
BOOL adIsShowing = [[[super webView] superview].subviews containsObject:self.adView];
if (adIsShowing) {
if (self.bannerAtTop) {
webViewFrame.origin.y = adViewFrame.size.height;
} else {
webViewFrame.origin.y = 0;
CGRect adViewFrame = self.adView.frame;
CGRect superViewFrame = [[super webView] superview].frame;
adViewFrame.origin.y = (self.isLandscape ? superViewFrame.size.width : superViewFrame.size.height) - adViewFrame.size.height;
self.adView.frame = adViewFrame;
}
webViewFrame.size.height = self.isLandscape? (superViewFrame.size.width - adViewFrame.size.height) : (superViewFrame.size.height - adViewFrame.size.height);
} else {
webViewFrame.size = self.isLandscape? CGSizeMake(superViewFrame.size.height, superViewFrame.size.width) : superViewFrame.size;
webViewFrame.origin = CGPointZero;
}
//[UIView beginAnimations:#"blah" context:NULL];
//[UIView setAnimationDuration:0.5];
//[self.adView setAlpha:1.0];
//[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[super webView].frame = webViewFrame;
//[UIView commitAnimations];
}
}
#pragma mark -
#pragma mark Private Methods
- (void) __prepare:(BOOL)atTop
{
NSLog(#"CDViAd Prepare Ad, bannerAtTop: %d", atTop);
Class adBannerViewClass = NSClassFromString(#"ADBannerView");
if (adBannerViewClass && !self.adView) {
self.adView = [[ADBannerView alloc] initWithFrame:CGRectMake(0, 0, 320, 50)];
self.adView.requiredContentSizeIdentifiers = [NSSet setWithObjects: ADBannerContentSizeIdentifierPortrait, ADBannerContentSizeIdentifierLandscape, nil];
UIDeviceOrientation currentOrientation = [[UIDevice currentDevice] orientation];
if( UIInterfaceOrientationIsLandscape( currentOrientation ) ) {
self.isLandscape = YES;
self.adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierLandscape;
} else {
self.isLandscape = NO;
self.adView.currentContentSizeIdentifier = ADBannerContentSizeIdentifierPortrait;
}
self.adView.delegate = self;
self.adView.backgroundColor = [UIColor blackColor];
//[self.webView.superview addSubview:self.adView];
self.webView.superview.backgroundColor = [UIColor blackColor];
self.bannerAtTop = atTop;
self.bannerIsVisible = NO;
self.bannerIsInitialized = YES;
[self resizeViews];
}
}
- (void) __showAd:(BOOL)show
{
NSLog(#"CDViAd Show Ad: %d", show);
if (!self.bannerIsInitialized){
[self __prepare:NO];
}
if (!(NSClassFromString(#"ADBannerView") && self.adView)) { // ad classes not available
return;
}
if (show == self.bannerIsVisible) { // same state, nothing to do
return;
}
if (show) {
//[UIView beginAnimations:#"blah" context:NULL];
//[UIView setAnimationDuration:0.5];
//[self.adView setAlpha:1.0];
//[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[[[super webView] superview] addSubview:self.adView];
[[[super webView] superview] bringSubviewToFront:self.adView];
[self resizeViews];
//[UIView commitAnimations];
self.bannerIsVisible = YES;
} else {
//[UIView beginAnimations:#"blah" context:NULL];
//[UIView setAnimationDuration:0.5];
//[self.adView setAlpha:0.0];
//[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[self.adView removeFromSuperview];
[self resizeViews];
//[UIView commitAnimations];
self.bannerIsVisible = NO;
}
}
#pragma mark -
#pragma ADBannerViewDelegate
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner willLeaveApplication:(BOOL)willLeave
{
NSLog(#"Banner view begining action");
[self writeJavascript:#"cordova.fireDocumentEvent('onClickAd');"];
if (!willLeave) {
}
return YES;
}
- (void)bannerViewActionDidFinish:(ADBannerView *)banner
{
NSLog(#"Banner view finished action");
}
- (void)bannerViewDidLoadAd:(ADBannerView *)banner
{
NSLog(#"Banner Ad loaded");
Class adBannerViewClass = NSClassFromString(#"ADBannerView");
if (adBannerViewClass) {
if (!self.bannerIsVisible) {
[self __showAd:YES];
}
[self writeJavascript:#"cordova.fireDocumentEvent('onReceiveAd');"];
}
}
- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError*)error
{
NSLog(#"Banner failed to load Ad");
Class adBannerViewClass = NSClassFromString(#"ADBannerView");
if (adBannerViewClass) {
//if ( self.bannerIsVisible ) {
// [self __showAd:NO];
//}
NSString *jsString =
#"cordova.fireDocumentEvent('onFailedToReceiveAd',"
#"{ 'error': '%#' });";
[self writeJavascript:[NSString stringWithFormat:jsString, [error description]]];
}
}
- (void)dealloc {
[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:UIDeviceOrientationDidChangeNotification
object:nil];
self.adView.delegate = nil;
self.adView = nil;
}
#end
Edited code suggested by Rich Tolley:
- (CDVPlugin *)initWithWebView:(UIWebView *)theWebView {
self = (CDViAd *)[super initWithWebView:theWebView];
if (self) {
// These notifications are required for re-placing the ad on orientation
// changes. Start listening for notifications here since we need to
// translate the Smart Banner constants according to the orientation.
/*[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(deviceOrientationChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];*/
}
return self;
}
- (void)dealloc {
/*[[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:UIDeviceOrientationDidChangeNotification
object:nil];*/
self.adView.delegate = nil;
self.adView = nil;
}
You are getting the notifications because they are device orientation updates - rather than interface orientation updates. Even though the UI doesn't change orientation, the device can, so you still get updates.
The crash is because the code is missing the notification method:
- (void)deviceOrientationChange:(NSNotification *)notification.
Looking at the source of CDViAid.m, it subscribes to a notification but declares no method to handle it:
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(deviceOrientationChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
(There's nothing to deal with this on the superclass either)
There's a bug report for this at: https://github.com/floatinghotpot/cordova-plugin-iad/issues/1, and a suggested solution, which appears to duplicate willRotateToInterfaceOrientation.
You could just remove the notification adding and removing code from -init and -dealloc - since it has no method to call, it can't be doing anything.
I am trying to develop a realtime multiplayer game for IOS by using cocos2d by using the tutorial on http://www.raywenderlich.com/3325/how-to-make-a-simple-multiplayer-game-with-game-center-tutorial-part-22
Everything works fine including auto matching with a random player but inviting a friend doesn't work because other device cannot receive an invitation.
When I clicked on invite friends button and then selected a friend by using the standard game center interface, it says waiting (forever) and nothing happens. My friend cannot receive an invitation from game center (no notifications).
I can invite a friend by using nearby friends functionality (when this functionality is enabled on both devices) but no invitation notification when nearby friends is disabled.
I spent hours and hours for searching on Google, found similar cases but no solution.
Some early feedback about possible answers:
I use two devices (one iPhone and one iPad), no simulator
All settings on iTunes connect are fine including multiplayer settings
I validated that both devices are connected to sandbox by using different test accounts
I've already checked the notification settings for Game center on both devices
I've already checked all proxy/firewall issues and tried on both WiFi and Cellular for both devices
Game invitations are enabled for both of the devices/accounts
I've already checked the bundle IDs, app version IDs, etc...
Both of the devices are IOS 6.x and App target version os IOS 5.0
I have no other issues about game center (leaderboards, random matchmaking, etc... all fine)
I call the inviteHandler method as soon after I authenticated a user as possible as mentioned in Apple documentation.
Here is my Game center helper class Header file:
#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>
#protocol GCHelperDelegate
- (void)matchStarted;
- (void)matchEnded;
- (void)match:(GKMatch *)match didReceiveData:(NSData *)data
fromPlayer:(NSString *)playerID;
- (void)inviteReceived;
#end
#interface GCHelper : NSObject <GKMatchmakerViewControllerDelegate, GKMatchDelegate>{
BOOL gameCenterAvailable;
BOOL userAuthenticated;
UIViewController *presentingViewController;
GKMatch *match;
BOOL matchStarted;
id <GCHelperDelegate> delegate;
NSMutableDictionary *playersDict;
GKInvite *pendingInvite;
NSArray *pendingPlayersToInvite;
NSMutableArray *unsentScores;
}
#property (retain) GKInvite *pendingInvite;
#property (retain) NSArray *pendingPlayersToInvite;
#property (assign, readonly) BOOL gameCenterAvailable;
#property (retain) NSMutableDictionary *playersDict;
#property (retain) UIViewController *presentingViewController;
#property (retain) GKMatch *match;
#property (assign) id <GCHelperDelegate> delegate;
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers
viewController:(UIViewController *)viewController
delegate:(id<GCHelperDelegate>)theDelegate;
- (BOOL) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent;
+ (GCHelper *)sharedInstance;
- (void)authenticateLocalUser;
#end
And here is the implementation of the game center helper class
#import "GCHelper.h"
#implementation GCHelper
#synthesize gameCenterAvailable;
#synthesize presentingViewController;
#synthesize match;
#synthesize delegate;
#synthesize playersDict;
#synthesize pendingInvite;
#synthesize pendingPlayersToInvite;
#pragma mark Initialization
static GCHelper *sharedHelper = nil;
+ (GCHelper *) sharedInstance {
if (!sharedHelper) {
sharedHelper = [[GCHelper alloc] init];
}
return sharedHelper;
}
- (BOOL)isGameCenterAvailable {
// check for presence of GKLocalPlayer API
Class gcClass = (NSClassFromString(#"GKLocalPlayer"));
// check if the device is running iOS 4.1 or later
NSString *reqSysVer = #"4.1";
NSString *currSysVer = [[UIDevice currentDevice] systemVersion];
BOOL osVersionSupported = ([currSysVer compare:reqSysVer
options:NSNumericSearch] != NSOrderedAscending);
return (gcClass && osVersionSupported);
}
- (id)init {
if ((self = [super init])) {
gameCenterAvailable = [self isGameCenterAvailable];
if (gameCenterAvailable) {
NSNotificationCenter *nc =
[NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(authenticationChanged)
name:GKPlayerAuthenticationDidChangeNotificationName
object:nil];
}
}
return self;
}
- (void)authenticationChanged {
if ([GKLocalPlayer localPlayer].isAuthenticated && !userAuthenticated) {
NSLog(#"Authentication changed: player authenticated.");
userAuthenticated = TRUE;
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
NSLog(#"Received invite");
self.pendingInvite = acceptedInvite;
self.pendingPlayersToInvite = playersToInvite;
[delegate inviteReceived];
};
} else if (![GKLocalPlayer localPlayer].isAuthenticated && userAuthenticated) {
NSLog(#"Authentication changed: player not authenticated");
userAuthenticated = FALSE;
}
}
- (void)lookupPlayers {
NSLog(#"Looking up %d players...", match.playerIDs.count);
[GKPlayer loadPlayersForIdentifiers:match.playerIDs withCompletionHandler:^(NSArray *players, NSError *error) {
if (error != nil) {
NSLog(#"Error retrieving player info: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
} else {
// Populate players dict
self.playersDict = [NSMutableDictionary dictionaryWithCapacity:players.count];
for (GKPlayer *player in players) {
NSLog(#"Found player: %#", player.alias);
[playersDict setObject:player forKey:player.playerID];
}
// Notify delegate match can begin
matchStarted = YES;
[delegate matchStarted];
}
}];
}
#pragma mark User functions
- (void)authenticateLocalUser {
if (!gameCenterAvailable) return;
NSLog(#"Authenticating local user...");
if ([GKLocalPlayer localPlayer].authenticated == NO) {
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:nil];
} else {
NSLog(#"Already authenticated!");
}
}
- (void)findMatchWithMinPlayers:(int)minPlayers maxPlayers:(int)maxPlayers viewController:(UIViewController *)viewController delegate:(id<GCHelperDelegate>)theDelegate {
if (!gameCenterAvailable) return;
matchStarted = NO;
self.match = nil;
self.presentingViewController = viewController;
delegate = theDelegate;
if (pendingInvite != nil) {
[presentingViewController dismissModalViewControllerAnimated:NO];
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:pendingInvite] autorelease];
mmvc.matchmakerDelegate = self;
[presentingViewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
} else {
[presentingViewController dismissModalViewControllerAnimated:NO];
GKMatchRequest *request = [[[GKMatchRequest alloc] init] autorelease];
request.minPlayers = minPlayers;
request.maxPlayers = maxPlayers;
request.playersToInvite = pendingPlayersToInvite;
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithMatchRequest:request] autorelease];
mmvc.matchmakerDelegate = self;
[presentingViewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
}
}
#pragma mark GKMatchmakerViewControllerDelegate
// The user has cancelled matchmaking
- (void)matchmakerViewControllerWasCancelled:(GKMatchmakerViewController *)viewController {
[presentingViewController dismissModalViewControllerAnimated:YES];
}
// Matchmaking has failed with an error
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFailWithError:(NSError *)error {
[presentingViewController dismissModalViewControllerAnimated:YES];
NSLog(#"Error finding match: %#", error.localizedDescription);
}
// A peer-to-peer match has been found, the game should start
- (void)matchmakerViewController:(GKMatchmakerViewController *)viewController didFindMatch:(GKMatch *)theMatch {
[presentingViewController dismissModalViewControllerAnimated:YES];
self.match = theMatch;
match.delegate = self;
if (!matchStarted && match.expectedPlayerCount == 0) {
NSLog(#"Ready to start match!");
[self lookupPlayers];
}
}
#pragma mark GKMatchDelegate
// The match received data sent from the player.
- (void)match:(GKMatch *)theMatch didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID {
if (match != theMatch) return;
[delegate match:theMatch didReceiveData:data fromPlayer:playerID];
}
// The player state changed (eg. connected or disconnected)
- (void)match:(GKMatch *)theMatch player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (match != theMatch) return;
switch (state) {
case GKPlayerStateConnected:
// handle a new player connection.
NSLog(#"Player connected!");
if (!matchStarted && theMatch.expectedPlayerCount == 0) {
NSLog(#"Ready to start match!");
[self lookupPlayers];
}
break;
case GKPlayerStateDisconnected:
// a player just disconnected.
NSLog(#"Player disconnected!");
matchStarted = NO;
[delegate matchEnded];
break;
}
}
// The match was unable to connect with the player due to an error.
- (void)match:(GKMatch *)theMatch connectionWithPlayerFailed:(NSString *)playerID withError:(NSError *)error {
if (match != theMatch) return;
NSLog(#"Failed to connect to player with error: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
}
// The match was unable to be established with any players due to an error.
- (void)match:(GKMatch *)theMatch didFailWithError:(NSError *)error {
if (match != theMatch) return;
NSLog(#"Match failed with error: %#", error.localizedDescription);
matchStarted = NO;
[delegate matchEnded];
}
- (void)reportScore:(int64_t)score forCategory:(NSString *)category {
// Only execute if OS supports Game Center & player is logged in
if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated == YES)
{
// Create score object
GKScore *scoreReporter = [[[GKScore alloc] initWithCategory:category] autorelease];
// Set the score value
scoreReporter.value = score;
// Try to send
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error != nil)
{
// Handle reporting error here by adding object to a serializable array, to be sent again later
[unsentScores addObject:scoreReporter];
}
}];
}
}
- (BOOL) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent {
if ([self isGameCenterAvailable] && [GKLocalPlayer localPlayer].authenticated == YES)
{
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier: identifier] autorelease];
if (achievement)
{
achievement.percentComplete = percent;
[achievement reportAchievementWithCompletionHandler:^(NSError *error)
{
if (error != nil)
{
// Retain the achievement object and try again later (not shown).
}
}];
}
return YES;
}
return NO;
}
#end
And Finally this is how I call the game center from my game layer (I tried two different options but none of them worked)
Option 1
[[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController: [[[UIApplication sharedApplication] keyWindow] rootViewController] delegate: self];
Option 2
AppController *app = (AppController*) [[UIApplication sharedApplication] delegate];
UINavigationController *viewController = [app navController];
[[GCHelper sharedInstance] findMatchWithMinPlayers:2 maxPlayers:2 viewController:viewController delegate:self];
Any help will be appreciated. Thanks in advance...
I've been dealing with multiplayer for a few months now and invitations have been a real issue for me....and like you, I used Ray's tutorial to get me started. I realized today that Ray's code has a bug in it where invites will not work if both clients have the GKMatchmakerView up. You need to dismiss it when you first receive the invite with something along the lines of:
[gcdelegate.viewController dismissViewControllerAnimated:YES
completion:^{
GKMatchmakerViewController *mmvc = [[GKMatchmakerViewController alloc] initWithInvite:pendingInvite];
mmvc.matchmakerDelegate = self;
[gcdelegate.viewController presentModalViewController:mmvc animated:YES];
self.pendingInvite = nil;
self.pendingPlayersToInvite = nil;
boo_invite=true;
}];
Ok, it seems that it again works without any changes in our code or network settings. I opened ticket to Apple support, bug records, etc... and it seems that some of them worked...
Now we understand that it was a bug in Game Center sandbox. As far as I see, sanbox version of game center is not that stable and people in Apple don't give enough attention to this service. There is also no way to check the system status on the internet.
I'm still continuing to discuss with Apple support in order to understand the reason and will share the all conversation here when it's completed.
Happy coding...