iOS 6 Game Center authenticateHandler can't login after a cancel - ios6

When using the authenticateHandler in iOS 6, game center won't present the login view if the user cancels it. I realize game center will auto lockout an app after 3 cancel attempts, but I'm talking about just 2 attempts. If they cancel the login, they have to leave the app and come back before game center will present the login even through the authenticateHandler is getting set again. Any ideas on how to handle this case in iOS 6?
It works fine when using the older authenticateWithCompletionHandler method:
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_6_0
GKLocalPlayer.localPlayer.authenticateHandler = authenticateLocalPlayerCompleteExtended;
#else
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:authenticateLocalPlayerComplete];
#endif
The reason this is important for my app is that it requires Game Center for multi-player. The app tries to authenticate to game center on launch, but if the user cancels we don't ask them at launch again so they won't get nagged. What we do is show a Game Center Login button if they aren't logged in when they select multi-player. The login button forces a game center login by calling authenticateWithCompletionHandler (and now by setting GKLocalPlayer.localPlayer.authenticateHandler again).

Better use runtime checks (instancesRespondToSelector:) instead of preprocessor #if statements, so that you can use recommended methods where they are available and depreciated ones elsewhere. I actually found I need to distinguish three cases before setting the invite handler, as the authentication handler might also get called with a nil view controller:
-(void)authenticateLocalPlayer
{
if ([[GKLocalPlayer class] instancesRespondToSelector:#selector(setAuthenticateHandler:)]) {
[[GKLocalPlayer localPlayer] setAuthenticateHandler:^(UIViewController *gameCenterLoginViewController, NSError *error) {
if (gameCenterLoginViewController) {
[self.presentedViewController presentViewController:gameCenterLoginViewController
animated:YES
completion:^{
[self setInviteHandlerIfAuthenticated];
}];
} else {
[self setInviteHandlerIfAuthenticated];
}
}];
} else { // alternative for iOS < 6
[[GKLocalPlayer localPlayer] authenticateWithCompletionHandler:^(NSError *error) {
[self setInviteHandlerIfAuthenticated];
}];
}
}
Yet more cases must be distinguished within the invite handler, as matchForInvite:: is new in iOS6 as well and avoids yet another round through game center view controllers:
-(void)setInviteHandlerIfAuthenticated
{
if ([GKLocalPlayer localPlayer].isAuthenticated) {
[GKMatchmaker sharedMatchmaker].inviteHandler = ^(GKInvite *acceptedInvite, NSArray *playersToInvite) {
if (acceptedInvite) {
if ([GKMatchmaker instancesRespondToSelector:#selector(matchForInvite:completionHandler:)]) {
[self showInfoAnimating:YES completion:NULL];
[[GKMatchmaker sharedMatchmaker] matchForInvite:acceptedInvite
completionHandler:^(GKMatch *match, NSError *error) {
// ... handle invited match
}];
} else {
// alternative for iOS < 6
GKMatchmakerViewController *mmvc = [[[GKMatchmakerViewController alloc] initWithInvite:acceptedInvite] autorelease];
mmvc.matchmakerDelegate = self;
// ... present mmvc appropriately
// ... handle invited match found in delegate method matchmakerViewController:didFindMatch:
}
} else if (playersToInvite) {
// ... handle match initiated through game center
}
};
}
}
Let me know if this helps.

I dont' think this is possible in iOS 6.0. There were API calls to do this in the early SDK builds that were removed before release.
In the WWDC 2012 Video: Session 516 - Integrating Your Games with Game Center [8:30] They actually show code where you call an authenticate method:
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
localPlayer.authenticationHandler = //handle the callback...
[localPlayer authenticate];
This method is now private API but you can see it in action by calling:
[[GKLocalPlayer localPlayer] performSelector:#selector(_authenticate)];
It does exactly what you want, but can't be used because it's now private.
You can also trigger the authentication process by posting the UIApplicationWillEnterForegroundNotification notification:
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillEnterForegroundNotification object:[UIApplication sharedApplication]];
I assume it would be unadvisable to do this in live code.

Related

requestAuthorizationToShareTypes method not displaying Permissions prompt in iOS 8 Xcode 6

-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if ([HKHealthStore isHealthDataAvailable]){
NSSet *writeDataTypes = [self dataTypesToWrite];
NSSet *readDataTypes = [self dataTypesToRead];
[self.healthStore requestAuthorizationToShareTypes:writeDataTypes readTypes:readDataTypes completion:^(BOOL success, NSError *error) {
NSLog(#"%s",__func__);
if (!success) {
NSLog(#"You didn't allow HealthKit to access these read/write data types. In your app, try to handle this error gracefully when a user decides not to provide access. The error was: %#. If you're using a simulator, try it on a device.", error);
return;
}
dispatch_async(dispatch_get_main_queue(), ^{
// Update the user interface based on the current user's health information.
NSLog(#"=========================== %s",__func__);
});
}];
}
}
requestAuthorizationToShareTypes does not calling back completion method.
I had a similar issue with the permissions box not appearing and hadn't set up the HKHealthStore properly, putting this beforehand fixed it for me
self.healthStore = [[HKHealthStore alloc] init];
Here is a sample implementation that returns types instead of strings as described in the comment section.
-(NSSet*)datatypesToWrite {
NSArray *quantityTypes =
#[HKQuantityTypeIdentifierHeartRate,
HKQuantityTypeIdentifierBodyTemperature,
HKQuantityTypeIdentifierBloodPressureSystolic,
HKQuantityTypeIdentifierBloodPressureDiastolic,
HKQuantityTypeIdentifierRespiratoryRate];
NSMutableArray *hkTypes = [[NSMutableArray alloc] init];
for (NSString *identifier in quantityTypes) {
HKQuantityType *quantType =
[HKObjectType quantityTypeForIdentifier:identifier];
[hkTypes addObject:quantType];
}
// Make sure the types are of the correct style (Quantity, Category, Etc.)
HKCategoryType *catType =
[HKObjectType categoryTypeForIdentifier:HKCategoryTypeIdentifierSleepAnalysis];
[hkTypes addObject:catType];
return [[NSSet alloc] initWithArray:hkTypes];
}
Each time you request new types for the first time the modal permissions dialog will show up (but it won't show up again if you re-prompt for permissions not granted). Apple's guidelines are to prompt for everything you might need, but it feels a bit against best practices to me to request 12 types up front if I know somebody only asked to save into a few of them.

iOS - how to properly load and display interstitial iAds?

our application shows interstitial iAds after each played level.
At the moment a level finishes, we create the ad:
_interstitial = [[ADInterstitialAd alloc] init];
_interstitial.delegate = self;
and the delegate implements the following callback methods:
- (void)interstitialAdDidUnload:(ADInterstitialAd *)interstitialAd
{
[self finish];
}
- (void)interstitialAd:(ADInterstitialAd *)interstitialAd didFailWithError:(NSError *)error
{
[self finish];
}
- (void)interstitialAdDidLoad:(ADInterstitialAd *)interstitialAd
{
[_interstitial presentFromViewController:self];
}
- (void)interstitialAdActionDidFinish:(ADInterstitialAd *)interstitialAd
{
[self finish];
}
whereas the finish method cleans up things and forwards the user to the main menu.
from the moment the ad object is created until the finish method is called, a spinning wheel is displayed on the screen.
Now the thing is that if no ad is loaded (for example if no internet connection is available) none of the callback functions ever gets called, so we also add a NSTimer when creating the Ad and check back after 10 seconds, like this:
_interstitial = [[ADInterstitialAd alloc] init];
_interstitial.delegate = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:#selector(checkForAds:)
userInfo:nil
repeats:false];
-(void)checkForAds:(NSTimer *)timer
{
[_timer invalidate];
if(!_interstitial.loaded)
[self finish];
}
but this seems very unclean - and also it leads to a 10 seconds spinning wheel delay after each level if no internet connection is available for example (or if the connection is very bad).
so, my questions:
what's the proper way to deal with this? I feel a timer here is not the right thing.
I'm currently considering to create the Ad object before the level starts, and then after the level finishes immediately check the _interstitial.loaded property and show the ad in this case or skip over it otherwise, without using a timer. The problem is that a level session can take quite long (between 1 and 60 minutes I'd say) and I read that ads can expire, however there is no indication in the documentation how long it usually takes until ads expire and what happens in this case. Will the _interstitial.loaded property return false if the ad expired in the meantime in the background? is some callback function called when the ad expires? Is it a viable approach to create the ad object before a level starts (that is, 1-60 minutes before the ad actually gets displayed) and later just check if _interstitial.loaded is true and in this case display the ad?

iOS Google Analytics missing first UIViewController

BLUF: Google Analytics iOS v2 beta 4 misses tracking my first UIViewController that is loaded while hitting successfully on subsequent UIViewControllers (subclassed with GAITrackedViewController)
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Optional: automatically send uncaught exceptions to Google Analytics.
[GAI sharedInstance].trackUncaughtExceptions = YES;
// Optional: set Google Analytics dispatch interval to e.g. 20 seconds.
[GAI sharedInstance].dispatchInterval = 0;
// Optional: set debug to YES for extra debugging information.
[GAI sharedInstance].debug = YES;
// Create tracker instance.
[[GAI sharedInstance] trackerWithTrackingId:#"UA-########-1"]; // id<GAITracker> tracker =
return YES;
}
MainViewController.m
- (void)viewDidLoad
{
NSLog(#"viewDidLoad");
[super viewDidLoad];
self.trackedViewName = #"First Screen";
//[[GAI sharedInstance] dispatch];
...
}
I do not call dispatch manually on the working UIViewController's, and I have tried both calling and not calling on the non-working. But it is a mystery. No logs are generated if I don't call it manually. When I do call dispatch manually I receive the following:
GoogleAnalytics 2.0b4 -[GAIDispatcher initiateDispatch:retryNumber:]
(GAIDispatcher.m:481) DEBUG: No pending hits.
Thanks in advance!
Turns out that you should really make sure that if you override any methods, you should make sure to call super on them as well...
I had written my own viewDidAppear method but had not called [super viewDidAppear:animated];. I guess that is where GoogleAnalytics does their magic. Hopefully this will help someone else not do this as well.
in my Case , I did call super method , but it did not work at some pages .
and I just change the order and let GA before the super method , It did work now .
- (void)viewDidAppear:(BOOL)animated {
self.screenName = #"mainMenu";
[super viewDidAppear:animated];
}
Contrary to Shu Zhang answer: super should be called first and then you should add other code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.screenName = #"YourViewControllerName";
//your implementation here
}
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
///your implementation here...
}

How to play song in an iOS6 app with mp3 format from an http link?

Recently i have started working on iOS application development. These days i am working on a Music Player which must have following functionality:
-Online buffering of the song from php web service.
-Play, pause, stop, next song, previous song.
-two sliders, one is for volume control and other is for showing the play time of the song.
-shuffle, repeat song.
I have tried these things with AVPlayer and AVAudioPlayer but in AVAudioPlayer it is not possible to stream the data from url i think because i have tried a lot then i done this by using AVplayer but it is not supporting options like volume control etc. and even the buffering is also not proper like a have to press play button again if the buffering stops at some point. I need an urgent help for this Audio Player any tutorial any example which i can understand easily as i am new in this field.
Here is the code
NSURL *url = [[NSURL alloc] initWithString:#"http://www.androidmobiapps.com/extrememusic/app/songs/1.mp3"];
[self setupAVPlayerForURL:url];
I am calling this song at viewdidload
-(void) setupAVPlayerForURL: (NSURL*) url {
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:nil];
AVPlayerItem *anItem = [AVPlayerItem playerItemWithAsset:asset];
audioPlayer = [AVPlayer playerWithPlayerItem:anItem];
[audioPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == audioPlayer && [keyPath isEqualToString:#"status"]) {
if (audioPlayer.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (audioPlayer.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayer Ready to Play");
} else if (audioPlayer.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
}
I've done functions you mentioned above as a class, and open source it. Whatever you wanna use the class directly or treat it as an AVFoundation beginner examples. Good luck!
Watch this repo on GitHub here

C# properties settings works from the second time on

Here's the problem: I've published an app made with Windows Forms with ClickOnce. Therefore, I can't show Terms of Service before install. The client decided to show some Terms of Service page after the install, and if the user agreed with the terms by pressing the button, show the main app. Problem is, after install the app shows the ToS, the user accepts, and after the first restart the user is shown the ToS again! If he accepts, the next time the user opens the app (either after a restart or after the app is closed and opened again) this problem will no longer appear, i.e. if the user agreed to the ToS the main app will show up, without asking the ToS.
The code defining the tos in settings:
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool tos {
get {
return ((bool)(this["tos"]));
}
set {
this["tos"] = value;
}
}
The code that checks for ToS when the app starts:
bool tosB = Properties.Settings.Default.tos;
if (!tosB)
{
this.tab_control.SelectTab(this.TOS_PAGE);
this.richTextBox1.Rtf =
global::MyAppName.Properties.Resources.terms_and_conditions;
}
else
{
check_connectivity();
}
The code that runs when the user cliks the I agree button:
private void button2_Click(object sender, EventArgs e)
{
Properties.Settings.Default.tos = true;
Properties.Settings.Default.Save();
Microsoft.Win32.RegistryKey key =
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", true);
key.SetValue("My App Name", Application.ExecutablePath.ToString());
check_connectivity();
}

Resources