After a while playing my iPhone game the phone starts to get really hot, and a while after that the app suddenly gets really slow and laggy, before eventually crashing.
I have followed official docs when implementing Admob ads.
I know for a fact the problem is Admob, since the app functions perfectly when turning off ads.
I know there is quite a lot of ads showing if you play for a while, but limiting the amount of ads displaying will only delay the lag and crash, and will not fix the underlying issue.
This is the code I use to load and display the ads:
- (void)viewDidLoad {
[super viewDidLoad];
[self loadInterstitial];
}
- (void)loadInterstitial {
[GADInterstitialAd loadWithAdUnitID:#"ca-app-pub-xxx/xxx" request:[GADRequest request] completionHandler:^(GADInterstitialAd *ad, NSError *error) {
if (error) {
NSLog(#"Failed to load interstitial ad with error: %#", [error localizedDescription]);
return;
}
self.interstitial.fullScreenContentDelegate = nil;
self.interstitial = ad;
self.interstitial.fullScreenContentDelegate = self;
}];
}
- (void)viewDidAppear:(BOOL)animated {
adCount = [[NSUserDefaults standardUserDefaults] integerForKey:#"adCount"] + 1;
[[NSUserDefaults standardUserDefaults] setInteger:adCount forKey:#"adCount"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (adCount > 11) {
[self displayInterstitial];
}
}
- (void)displayInterstitial {
if (self.interstitial) {
[self.interstitial presentFromRootViewController:self];
adCount = 0;
[[NSUserDefaults standardUserDefaults] setInteger:adCount forKey:#"adCount"];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
NSLog(#"Ad wasn't ready");
}
}
- (void)adDidDismissFullScreenContent:(nonnull id<GADFullScreenPresentingAd>)ad {
[self loadInterstitial];
}
Nothing special. To me it seems like the memory gets overloaded or too many leaks after displaying enough interstitial ads.
Related
I have a 'Terms and Conditions' controller I represent in the first launch of the app, but when opening the app in the first time from the App Store page the Terms and Conditions' controller not shown- only after I close the app and reopen it from the device itself (not the App Store page) then the controller is shown.
this code is from the launched screen controller:
- (void)viewDidLoad {
[self agreedToServerTerms];
}
- (void)agreedToServerTerms {
[[HttpUtils instance] httpRequest:TERMS_AGREEMENT_URL :params completionHandler:^(NSData *data, NSError *error) {
#try {
bool acceptTerms = false;
if (error) {
[Utils log:[NSString stringWithFormat:#"agreedToServerTerms error=%#", error]];
} else {
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!error) {
acceptTerms = [jsonData.allValues[0] boolValue];
if (!acceptTerms) {
TermsAndConditionController *tac = [[TermsAndConditionController alloc] initWithNibName:#"TermsAndConditionController" bundle:nil];
tac.delegate = self;
[[SlideNavigationController sharedInstance] pushViewController:tac animated:false];
}
else {
[self performSelectorInBackground:#selector(initialApp) withObject:nil];
}
} else {
[Utils log:[NSString stringWithFormat:#"parsing jsonData error = %#" ,error]];
}
}
} #catch (NSException *e) {
[Utils log:[NSString stringWithFormat:#"Exception occurred: %#, %#", e, [e userInfo]]];
}
}];
}
from App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
LaunchScreenController *bbp = [[LaunchScreenController alloc] initWithNibName:#"LaunchScreenController" bundle:nil];
[[SlideNavigationController sharedInstance] pushViewController:bbp animated:YES];
if (_launchedURL) {
bbp.launchedURL = _launchedURL;
}
[self.window addSubview:bbp.view];
}
herewith how I do almost the exact same thing. I think you need to rework yours, it could be a bit different, but this may help.
In the first VC that my app displays, in its viewWillAppear message, I have the following code
// Terms of use
if ( [NSUserDefaults.standardUserDefaults integerForKey:#"tou"] != 20170614 )
{
[self performSegueWithIdentifier:#"tou" sender:nil];
[NSUserDefaults.standardUserDefaults setInteger:20170614 forKey:#"tou"];
}
This uses user defaults to note if the terms of use have ever been presented and, if so, it marks #"tou" with some arbitrary value. In your case you can set the logic in your controller and only mark it once the user accepts.
This requires a segue in the storyboard called #"tou" that will present your T&C controller and you may need to prevent exit if the user does not agree, but the idea is to segue away from your first VC if the user did not agree yet to present the user with the T&C rather than injecting it into the app delegate as you do at present.
I am using Admob for interstitials in my iOS app. I have a test ad set up and it works correctly only the first time I go through the app flow. Subsequent attempts do not show the test ad or call the delegate methods. Below is the GADInterstitial ad setup in my UIViewController
ViewController.h
#interface ViewController : UIViewController <GADInterstitialDelegate>
#property(nonatomic, strong) GADInterstitial *interstitial;
ViewController.m
-(void)viewDidLoad{
self.interstitial = [[GADInterstitial alloc] initWithAdUnitID:#"ca-app-pub-//adID"];
self.interstitial.delegate = self;
GADRequest *request = [GADRequest request];
// Requests test ads on test devices.
request.testDevices = #[#"//test device ID"];
[self.interstitial loadRequest:request];
}
I call the ad from an IBAction. If the user has paid for the app or has used the IBAction under a number of times, then the ad does not show. If user has not paid or used the IBAction over a certain number of times, the ad shows. NSUserDefaults does the check, and if user does IBAction under a certain # of times, fires another method, and increments NSUserDefaults to keep track of how many times user has performed action. I tried using other ad networks, and this same NSUserDefault check worked fine.
In the ViewController.m viewDidLoad.
-(void)action{
if (([[NSUserDefaults standardUserDefaults] integerForKey:#"AdShow"] < 2) || ([[NSUserDefaults standardUserDefaults] integerForKey:#"DidBuyInAppPurchase"] == 1)) {
[self doOtherAction];
NSInteger integer = [[NSUserDefaults standardUserDefaults]integerForKey:#"AdShow"];
NSInteger newInteger = integer + 1;
[[NSUserDefaults standardUserDefaults] setInteger:newInteger forKey:#"AdShow"];
}
else{
NSInteger newInteger = 0;
[[NSUserDefaults standardUserDefaults] setInteger:newInteger forKey:#"AdShow"];
if ([self.interstitial isReady]) {
[self.interstitial presentFromRootViewController:self];
}
}
}];
}
The delegate methods are below also in viewDidLoad
- (void)interstitialWillPresentScreen:(GADInterstitial *)ad {
}
- (void)interstitialWillDismissScreen:(GADInterstitial *)ad {
NSLog(#"interstitialWillDismissScreen");
}
- (void)interstitialDidDismissScreen:(GADInterstitial *)ad {
[self doOtherAction];
}
- (void)interstitial:(GADInterstitial *)ad
didFailToReceiveAdWithError:(GADRequestError *)error {
[self doOtherAction];
NSLog(#"interstitial:didFailToReceiveAdWithError: %#", [error localizedDescription]);
}
The GADInterstitial should go in viewWillAppear not viewDidLoad. For some reason putting it in viewDidLoad causes it not to load with the ad.
I have successfully removed ads from the app with an in app purchase.
The problem is that if I close the app and reopen. The ads start up again.
I have 2 main scenes. The GameOverScene and the GameScene. The In App Purchase happens in the GameOverScene.
GameOverScene.m :
- (void)OnRemoveADS {
[self showPurchaseAlert: IAP_Q_RemoveADS :0];
g_bRemoveADS = [[NSUserDefaults standardUserDefaults] boolForKey: #"REMOVEADS"];
// For HZInterstitialAd, HZVideoAd, and HZIncentivizedAd, just check the BOOL to see if an ad should be shown
if (!g_bRemoveADS) {
[HZInterstitialAd show];
[self removeBannerAds];
[self disableAds];
NSLog(#"Disable ads is called");
}
}
- (void)removeBannerAds {
HZBannerAdOptions *options = [[HZBannerAdOptions alloc] init];
[HZBannerAd placeBannerInView:self.view
position:HZBannerPositionBottom
options:options
success:^(HZBannerAd *banner) {
if (g_bRemoveADS) { // case (2)
// Just discard the banner
[banner setHidden: YES];
[banner removeFromSuperview];
banner = nil;
//_currentBannerAd = banner;
NSLog(#"Banner ad removed!GameOverScene");
} else {
// Keep a reference to the current banner ad, so we can remove it from screen later if we want to disable ads.
_currentBannerAd = banner;
}
NSLog(#"Ad Shown! GameOverScene");
}
failure:^(NSError *error) {
NSLog(#"Error = %#",error);
}];
}
- (void)disableAds {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"REMOVEADS"];
[_currentBannerAd removeFromSuperview]; // case (3)
}
GameScene.m :
-(id) init {
if (self = [super init]) {
if (!g_bRemoveADS) {
g_bRemoveADS=FALSE;
[[NSUserDefaults standardUserDefaults] setBool:g_bRemoveADS forKey:#"REMOVEADS"];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
g_bRemoveADS=TRUE;
[[NSUserDefaults standardUserDefaults] setBool:g_bRemoveADS forKey:#"REMOVEADS"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}
}
The way I'm trying to solve it is by using the same code from the GameOverScene.m in the AppDelegate.m that way when the app starts up it will remove the ads.
AppDelegate.m :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
g_bRemoveADS = [[NSUserDefaults standardUserDefaults] boolForKey: #"REMOVEADS"];
if (!g_bRemoveADS) {
[HZInterstitialAd show];
[self disableAds];
NSLog(#"Disable ads is called");
}
}
Out of my perspective you have one negation to much.
if (!g_bRemoveADS) { should be replaced with if (g_bRemoveADS) { in GameOverScene.m.
if (g_bRemoveADS) {
[HZInterstitialAd show];
[self removeBannerAds];
[self disableAds];
NSLog(#"Disable ads is called");
}
g_bRemoveADS evaluates to TRUE when the respective user-default is set. When it is set, then you call the removeBannerAds stuff etc. which seems to be the deactivation action.
You have to synchronise your NSUserDefaults after changing changing a value in your disableAds method with:
[[NSUserDefaults standardUserDefaults]synchronize];
The Game Center tutorial I was following showed me everything except how to report the scores and achievements to the leaderboards.
This is the score part:
+ (void)reportScore:(Float64) score forIdentifier: (NSString*) identifier {
GKScore* highScore = [[GKScore alloc] initWithLeaderboardIdentifier:#"Tap_Competition_LB"];
highScore.value = score;
[GKScore reportScores:#[highScore] withCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"Error in reporting scores: %#", error);
}
}];
}
and this is the achievement part:
+ (void) reportAchievementIdentifier: (NSString*) identifier percentComplete: (float) percent
{
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier: identifier];
if (achievement)
{
achievement.percentComplete = percent;
achievement.showsCompletionBanner = YES;
if (![[NSUserDefaults standardUserDefaults] boolForKey:identifier]) {
//Tell analytics if you want to
}
NSArray *achievements = #[achievement];
[GKAchievement reportAchievements:achievements withCompletionHandler:^(NSError *error) {
if (error != nil) {
NSLog(#"Error in reporting achievements: %#", error);
} else {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:identifier];
[[NSUserDefaults standardUserDefaults] synchronize];
}
}];
}
}
Which are both located in the TCUser file I've created, after the authentication of the local user.
The tutorial I was following gave this line of code for reporting:
[SingletonClassName reportAchievementIdentifier:#"com.companyname.gamename.achievementname" percentComplete:100.0f];
But I don't understand which singleton class to reference. Can anyone help me out and show me how exactly to report a score or achievement?
The Apple documentation didn't help either.
PS This was the tutorial: http://www.raywenderlich.com/forums/viewtopic.php?f=2&t=10844
I created a little app just to test out Game center i sign in i am able to increase the score but when i look at the leader board nothing is there
here is my code
- (IBAction)increaseScore:(id)sender {
currentScore+=4;
scoreLabel.text=[NSString stringWithFormat:#"%i",currentScore];
}
- (IBAction)showLeaderBoard:(id)sender {
GKLeaderboardViewController *leaderBoardController = [[GKLeaderboardViewController alloc]init];
leaderBoardController.category=#"1";
leaderBoardController.timeScope=GKLeaderboardTimeScopeAllTime;
leaderBoardController.leaderboardDelegate=self;
[self presentModalViewController:leaderBoardController animated:YES];
}
- (void)leaderboardViewControllerDidFinish:(GKLeaderboardViewController *)viewController
{
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)sumbitScore:(id)sender {
GKScore *scoreReporter = [[GKScore alloc]initWithCategory:#"1"];
//#"1" is the leaderboard ID
scoreReporter.value=currentScore;
[scoreReporter reportScoreWithCompletionHandler:^(NSError *error) {
if (error) {
NSLog(#"submit failed");
}else{
NSLog(#"access granted");
}
}];
}
GameCenter will only show the leaderboard scores when there are two or more players on the leaderboard.
You will need a score submitted by another GameCenter account. You could sign up for a second Apple Id to be able to send in scores from a 'second' player.