I am experiencing a very weird issue with interstitials in cocos2d(version 3.1.0) This is the code I use to load both interstitials.
- (void)interstitialDidReceiveAd:(GADInterstitial *)interstitial {
[interstitial presentFromRootViewController:[CCDirector sharedDirector]];
interstitial.delegate = nil;
interstitial = nil;
}
- (void)interstitialAdDidLoad:(ADInterstitialAd *)interstitialAd {
[interstitialAd presentFromViewController:[CCDirector sharedDirector]];
}
Everything seems fine and works fine until you put and hold your finger on the display before the interstitial has popped, then when you cancel the touch(release the finger) and close the popup window the app becomes unresponsive, the touch handler does not work at all and you have to restart the app. I am struggling for several hours with no success of finding the issue causer. Could it be something with the CCDirector stop and start animation methods?
I found a workaround to the bug. What I did is to disable the respond manager before the google/iAD interstitial is shown, you can see the methods below.
- (void)interstitialDidReceiveAd:(GADInterstitial *)interstitial {
[[[CCDirector sharedDirector]responderManager]setEnabled:false];
[interstitial presentFromRootViewController:[CCDirector sharedDirector]];
interstitial.delegate = nil;
interstitial = nil;
}
- (void)interstitialAdDidLoad:(ADInterstitialAd *)interstitialAd {
[[[CCDirector sharedDirector]responderManager]setEnabled:false];
[interstitialAd presentFromViewController:[CCDirector sharedDirector]];
}
After that I enabled it again in the - (void) startAnimation in CCDirectorIOS.m file by putting the following code.
if (![[self responderManager]isEnabled]) {
[[self responderManager]setEnabled:true];
}
I know that this is not an elegant solution, because changing the library code directly is not always a good idea, but I could not find any other alternative. I think that this is a cocos2d bug, but I am not sure, may be someone more experienced can point out the exact issue. I have no idea if this bug is present in the newest cocos2d version.
EDIT: This behaviour is also present in the newest version.
Related
I have a little game with a timer.
I'm implementing adMob to monetize and I am not able to restart timer/ads after user clicks on the banner and come back to the app.
The flow is:
1 - game start
2 - show ads
3 - click on banner and pause timer
4 - oper safari
5 - click "back to my app" link/button (iOS feature)
6 - back to the app and restar timer (problem here)
I had implemented all adMob events method (and insert restar timer code) but I can't get out of this issue.
The code work because it worked with iAds (I'm migrating to adMob).
Any help is appreciated.
Thank you
EDIT:
here is the code:
/// Tells the delegate an ad request loaded an ad.
- (void)adViewDidReceiveAd:(GADBannerView *)adView {
NSLog(#"adViewDidReceiveAd");
self.pauseTimer = NO;
}
/// Tells the delegate an ad request failed.
- (void)adView:(GADBannerView *)adView
didFailToReceiveAdWithError:(GADRequestError *)error {
NSLog(#"adView:didFailToReceiveAdWithError: %#", [error localizedDescription]);
self.pauseTimer = NO;
}
/// Tells the delegate that a full screen view will be presented in response
/// to the user clicking on an ad.
- (void)adViewWillPresentScreen:(GADBannerView *)adView {
NSLog(#"adViewWillPresentScreen");
self.pauseTimer = NO;
}
/// Tells the delegate that the full screen view will be dismissed.
- (void)adViewWillDismissScreen:(GADBannerView *)adView {
NSLog(#"adViewWillDismissScreen");
self.pauseTimer = NO;
}
/// Tells the delegate that the full screen view has been dismissed.
- (void)adViewDidDismissScreen:(GADBannerView *)adView {
NSLog(#"adViewDidDismissScreen");
self.pauseTimer = NO;
}
/// Tells the delegate that a user click will open another app (such as
/// the App Store), backgrounding the current app.
- (void)adViewWillLeaveApplication:(GADBannerView *)adView {
NSLog(#"adViewWillLeaveApplication");
self.pauseTimer = YES;
}
In this VC create a property to store this
#property (nonatomic) BOOL didGoToSafari;
- (void)adViewWillLeaveApplication:(GADBannerView *)adView {
NSLog(#"adViewWillLeaveApplication");
self.pauseTimer = YES;
self.didGoToSafari = YES;
}
In the VC that you show right before the ad would show in viewWillAppear or viewDidAppear you should put this code
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(applicationDidBecomeActiveNotification:)
name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
And then after viewDidAppear or viewWillAppear, write this function
- (void)applicationDidBecomeActiveNotification:(NSNotification *)notification {
if (self.didGoToSafari = YES){
self.pauseTimer = NO;
self.didGoToSafari = NO;
}
}
In viewWillDisappear
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification
object:[UIApplication sharedApplication]];
Basically what you're doing is listening to see if the app became active again. If it did, check to see if it's coming back from Safari. It's not perfect because you could feasibly be using the app, user goes to Safari and then doesn't go back to or close the game. They could then use Safari later and then go back to the game and it would start running again. There probably some control flow in the AppDelegate you could use to code around this, but in general this code should do it.
EDIT: As per your comment about understanding it, here's the full explanation.
You are using NSNotification to listen for when the app returns to an active state. UIApplicationDidBecomeActiveNotification is automatically called when your app becomes active (it's an app delegate method). When it does, the method (void)applicationDidBecomeActiveNotification gets called automatically and the methods in that method get called. You have a boolean flag to see if the app is returning from Safari because your app could return from any other app if user switched to another app when the ad got pushed. In the end, you remove your VC as an observer to avoid memory leaks.
I have integrated the SKD of UnityAds into my game to show full screen interstitial video ads.
When the video is finished, a link to the AppStore is provided by the ad framework. When I select this link, the AppStore is opened. My app crashes in this moment, throwing EXC_BAD_ACCESS in CCGraphicsBufferGLUnsynchronized.
When starting my app, the UnityAds SDK is initialized like this:
[[UnityAds sharedInstance] startWithGameId:UNITYADS_MYAPP_ID
andViewController:[CCDirector sharedDirector]
];
As you can see, I am passing [CCDirector sharedDirector] as the view controller. I am mentioning this, as this might by a part of the problem?
Later I am calling the UnityAds SDK from within a Cocos2D scene like this:
-(void)showFullscreenAd {
// Stop Cocos2D rendering
[[CCDirector sharedDirector] pause];
[[CCDirector sharedDirector] stopAnimation];
// Is an ad available?
if ([[UnityAds sharedInstance] canShowAds]) {
// Display the ad
[[UnityAds sharedInstance] setZone:#"rewardedVideoZone"];
[[UnityAds sharedInstance] show];
}
}
As you can see I am stopping Cocos2D rendering before displaying the add.
When I select the AppStore link in the add, my app crashes.
This is the code Xcode points me to after the crash (in class CCGraphicsBufferGLUnsynchronized)
-(void)prepare
{
_count = 0;
GLenum target = (GLenum)_type;
glBindBuffer(_type, _buffer);
==> _ptr = _mapBufferRange(target, 0, (GLsizei)(_capacity*_elementSize), BUFFER_ACCESS_WRITE);
glBindBuffer(target, 0);
CC_CHECK_GL_ERROR_DEBUG();
}
Can someone point me into the right direction for debugging?
I am running my app on an iPad and iPhone under iOS 8.1.3
OK, I found a solution myself. Seems like the crash occurs because the app is in the background and rendering continues. So all I had to do was to make sure that rendering is not resumed after the app was moved to the background.
I do it like this now:
// Added a flag to control resuming of rendering
#property (readwrite) bool resumeRendering;
[...]
-(void)showFullscreenAd {
// Stop Cocos2D rendering
[[CCDirector sharedDirector] pause];
[[CCDirector sharedDirector] stopAnimation];
// Set to resume redering by default
_resumeRendering = YES;
// Is an ad available?
if ([[UnityAds sharedInstance] canShowAds]) {
// Display the ad
[[UnityAds sharedInstance] setZone:#"rewardedVideoZone"];
[[UnityAds sharedInstance] show];
}
}
[...]
#pragma mark - UnityAdsDelegate
// This method is called when the interstitial ad is discarded
// check if rendering should be resumed
-(void)unityAdsDidHide {
if ( _resumeRendering ) {
[[CCDirector sharedDirector] resume];
[[CCDirector sharedDirector] startAnimation];
}
// do anything else here
// In my case I transfer to the next scene
[...]
}
-(void)unityAdsWillLeaveApplication {
// Will leave application. Make sure rendering is not resumed anymore
_resumeRendering = NO;
}
When the user clicks the AppStore link in the ad now, the method unityAdsWillLeaveApplication will be called and I can mark resumeRendering as false.
Hope this helps other with the same problem.
iOS 7 bring the iAdAdditions category to UIViewController.
With it managing a banner is a matter of one line of code:
self.canDisplayBannerAds = YES;
But I wonder how to detect user touching the iAd banner. My need is to pause game behaviors (music, animations, timers...) while iAd is displayed full screen.
I have tried the following code:
- (void) viewWillDisappear:(BOOL)animated {
if ([self isPresentingFullScreenAd]) {
// view will disappear because of user action on iAd banner
}
else {
// view will disappear for any other reasons
}
}
- (void) viewWillAppear:(BOOL)animated {
if ([self isPresentingFullScreenAd]) {
// view will appear because full screen iAd — caused by previous user action on iAd banner — is dismissed
}
else {
// view will appear for other reasons
}
}
I have done some testings showing everything is OK. But I wonder if it's the correct way to implement it!
UPDATE
This is the solution I use in the production version of the application and everything is fine: no problems has showned.
You can use these specific delegates in your view controller, here's code ripped straight from my game where I toggle the audio/music off when a banner is clicked.
- (BOOL)bannerViewActionShouldBegin:(ADBannerView *)banner
willLeaveApplication:(BOOL)willLeave
{
[[CPAudioManager sharedInstance] toggleOnOffDependingOnSettings];
return YES;
}
-(void)bannerViewActionDidFinish:(ADBannerView *)banner
{
[[CPAudioManager sharedInstance] toggleOnOffDependingOnSettings];
}
For full screen interstitials the code is roughly the same, but you don't get such nice delegates.
You know when you are showing a full screen ad when you call requestInterstitialAdPresentation and it returns YES, e.g.
if( [self requestInterstitialAdPresentation]==YES ) {...}
You should pause your views, toggle your music off, and kill your banner view (probably).
Then, you know when the user has closed the interstitial in ViewDidAppear
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if( self.isPresentingFullScreenAd )
{
// we are about to stop presenting full screen ads
// unpause view
// enable banner ads
// turn music back on if it was playing before
}
else
{
// we are presenting the normal view or the full screen ad
}
}
I have an app that uses GameCenter in a very simple way (just a simple leaderboard with an all time high score). Sometimes when I switch to my app I'll see the notification saying "welcome back to Game Center" but sometimes this notification appears squished like in the following image:
http://i.imgur.com/KOCFIJo.jpg
Does anybody know what might the causing this? Because I have absolutely no idea.
My authentication code which generates the notification banner is fairly standard.
GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer];
[GKLocalPlayer localPlayer].authenticateHandler = ^(UIViewController *viewController, NSError *error) {
// If there is an error, do not assume local player is not authenticated.
if (localPlayer.isAuthenticated) {
// Enable Game Center Functionality
self.gameCenterAuthenticationComplete = YES;
[self enableGameCenter:YES];
gameCenterButton.enabled=true;
} else {
NSLog(#"game center not logged in");
// User has logged out of Game Center or can not login to Game Center, your app should run
// without GameCenter support or user interface.
self.gameCenterAuthenticationComplete = NO;
[self enableGameCenter:NO];
[self presentViewController:viewController animated:true completion:nil ];
gameCenterButton.enabled=false;
}
};
One additional piece of information is that my app is in portrait orientation when this problem occurs. It seems like if I rotate my phone 90 degrees while the banner is showing, it will look normally in landscape but in portrait it looks all squished. Does this help explain it?
I figured it out. I hadn't implemented preferredInterfaceOrientationForPresentation so I did that
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
And also I made sure that supportedInterfaceOrientations returend UIInterfaceOrientationMaskPortrait (note that it returns UIInterfaceOrientationMASKPortrait not just UIInterfaceOrientationPortrait). After that everything worked fine.
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskPortrait;
}
I have setup a test application using Admob Mediation service. I have an issue that when an error occurs and is handled by a method if I hide the banner View it causes no further ad requests to occur, possibly due to this 'hidden status'.
What I can do is sleep within the error method for a certain amount of time then request again, however this isn't the best method... I am guessing this would lock up some process and potentially other user input whilst sleepng? I am not sure of this as the app only includes ads so cannot test.
Here are my methods...
- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error;
{
NSLog(#"Failed to receive ad with error: %#", [error localizedFailureReason]);
bannerView_.hidden = YES;
sleep(59);
[bannerView_ loadRequest:[self createRequest]];
}
- (void)adViewDidReceiveAd:(GADBannerView *)view;
{
NSLog(#"Ad Received");
bannerView_.hidden = NO;
}
I am looking for the best way to either:
1. Hide the view when no ad is returned, but ensure requests continue and the ad view is shown again once an ad is received.
2. Use a loop in the error method to handle requesting again until successful and not locking up anything else.
Only being tested on simulator at the moment, if any difference is made.
I would suggest you to use performSelector to make an asynchronous call instead of using sleep because sleep will block your thread. So this is implemented as in the 1st way you mentioned.
- (void)adView:(GADBannerView *)view didFailToReceiveAdWithError:(GADRequestError *)error;
{
NSLog(#"Failed to receive ad with error: %#", [error localizedFailureReason]);
bannerView_.hidden = YES;
[self performSelector:#selector(repeatAdRequest) withObject:nil afterDelay:60.0];
}
- (void)adViewDidReceiveAd:(GADBannerView *)view;
{
NSLog(#"Ad Received");
bannerView_.hidden = NO;
}
-(void) repeatAdRequest
{
[bannerView_ loadRequest:[self createRequest]];
}
make addview a subview of another blank uiview.
In adview didfail, hide the view and not the adview
and in recieve show the view again.