From 531bee6efe73a500fe4fdff843b6398da4483df3 Mon Sep 17 00:00:00 2001 From: Justin Malandruccolo Date: Wed, 23 Aug 2023 12:24:59 -0700 Subject: [PATCH] Added UMP SDK integration to Rewarded Interstitial Objective-C & Swift samples PiperOrigin-RevId: 559507442 --- .../project.pbxproj | 6 + .../AppDelegate.m | 5 - .../Base.lproj/Main.storyboard | 30 ++++- .../GoogleMobileAdsConsentManager.h | 44 +++++++ .../GoogleMobileAdsConsentManager.m | 80 +++++++++++++ .../ViewController.h | 12 -- .../ViewController.m | 113 +++++++++++++++--- .../project.pbxproj | 6 + .../RewardedInterstitialExample/AppDelegate.m | 5 - .../Base.lproj/Main.storyboard | 30 ++++- .../GoogleMobileAdsConsentManager.h | 44 +++++++ .../GoogleMobileAdsConsentManager.m | 80 +++++++++++++ .../RewardedInterstitialExample/Info.plist | 2 + .../ViewController.h | 12 -- .../ViewController.m | 113 +++++++++++++++--- .../project.pbxproj | 4 + .../AppDelegate.swift | 6 +- .../Base.lproj/Main.storyboard | 42 +++++-- .../GameViewController.swift | 74 ++++++++++-- .../GoogleMobileAdsConsentManager.swift | 73 +++++++++++ .../project.pbxproj | 4 + .../AppDelegate.swift | 6 +- .../Base.lproj/Main.storyboard | 40 +++++-- .../GameViewController.swift | 74 ++++++++++-- .../GoogleMobileAdsConsentManager.swift | 73 +++++++++++ 25 files changed, 869 insertions(+), 109 deletions(-) create mode 100644 Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.h create mode 100644 Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.m create mode 100644 Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.h create mode 100644 Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.m create mode 100644 Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.swift create mode 100644 Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.swift diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj index 36917425..3c00fb4c 100644 --- a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + A8CC5E022A8ECECD0094B83E /* GoogleMobileAdsConsentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A8CC5E012A8ECECD0094B83E /* GoogleMobileAdsConsentManager.m */; }; CF9F261F1991396300F5C44E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9F261E1991396300F5C44E /* main.m */; }; CF9F26221991396300F5C44E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9F26211991396300F5C44E /* AppDelegate.m */; }; CF9F26251991396300F5C44E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9F26241991396300F5C44E /* ViewController.m */; }; @@ -15,6 +16,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + A8CC5E002A8ECECC0094B83E /* GoogleMobileAdsConsentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleMobileAdsConsentManager.h; sourceTree = ""; }; + A8CC5E012A8ECECD0094B83E /* GoogleMobileAdsConsentManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleMobileAdsConsentManager.m; sourceTree = ""; }; CF9F26191991396300F5C44E /* AdManagerRewardedInterstitialExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AdManagerRewardedInterstitialExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; CF9F261D1991396300F5C44E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CF9F261E1991396300F5C44E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -66,6 +69,8 @@ children = ( CF9F26201991396300F5C44E /* AppDelegate.h */, CF9F26211991396300F5C44E /* AppDelegate.m */, + A8CC5E002A8ECECC0094B83E /* GoogleMobileAdsConsentManager.h */, + A8CC5E012A8ECECD0094B83E /* GoogleMobileAdsConsentManager.m */, CF9F26231991396300F5C44E /* ViewController.h */, CF9F26241991396300F5C44E /* ViewController.m */, CF9F26261991396300F5C44E /* Main.storyboard */, @@ -154,6 +159,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A8CC5E022A8ECECD0094B83E /* GoogleMobileAdsConsentManager.m in Sources */, CF9F26251991396300F5C44E /* ViewController.m in Sources */, CF9F26221991396300F5C44E /* AppDelegate.m in Sources */, CF9F261F1991396300F5C44E /* main.m in Sources */, diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.m b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.m index f1c47f0f..0c01031c 100644 --- a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.m +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.m @@ -16,15 +16,10 @@ #import "AppDelegate.h" -#import - @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Initialize Google Mobile Ads SDK - [GADMobileAds.sharedInstance startWithCompletionHandler:nil]; - return YES; } diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard index e1fe15ca..7cbea2ca 100644 --- a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard @@ -1,12 +1,28 @@ - + - + + + + + + + + + + + + + + + + + @@ -36,7 +52,7 @@ @@ -59,10 +75,18 @@ + + + + + + + + diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.h b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.h new file mode 100644 index 00000000..9fb2d8e1 --- /dev/null +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.h @@ -0,0 +1,44 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// The Google Mobile Ads SDK provides the User Messaging Platform (Google's +/// IAB Certified consent management platform) as one solution to capture +/// consent for users in GDPR impacted countries. This is an example and +/// you can choose another consent management platform to capture consent. +@interface GoogleMobileAdsConsentManager : NSObject + +@property(class, atomic, readonly, strong, nonnull) GoogleMobileAdsConsentManager *sharedInstance; +@property(nonatomic, readonly) BOOL canRequestAds; +@property(nonatomic, readonly) BOOL isPrivacyOptionsRequired; + +/// Helper method to call the UMP SDK methods to request consent information and load/present a +/// consent form if necessary. +- (void)gatherConsentFromConsentPresentationViewController:(UIViewController *)viewController + consentGatheringComplete: + (void (^)(NSError *_Nullable error))completionHandler; + +/// Helper method to call the UMP SDK method to present the privacy options form. +- (void)presentPrivacyOptionsFormFromViewController:(UIViewController *)viewController + completionHandler: + (void (^)(NSError *_Nullable error))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.m b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.m new file mode 100644 index 00000000..17686087 --- /dev/null +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.m @@ -0,0 +1,80 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "GoogleMobileAdsConsentManager.h" + +#import +#import + + +@implementation GoogleMobileAdsConsentManager + ++ (instancetype)sharedInstance { + static GoogleMobileAdsConsentManager *shared; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[GoogleMobileAdsConsentManager alloc] init]; + }); + return shared; +} + +- (BOOL)canRequestAds { + return UMPConsentInformation.sharedInstance.canRequestAds; +} + +- (BOOL)isPrivacyOptionsRequired { + return UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus == + UMPPrivacyOptionsRequirementStatusRequired; +} + +- (void)gatherConsentFromConsentPresentationViewController:(UIViewController *)viewController + consentGatheringComplete: + (void (^)(NSError *_Nullable))consentGatheringComplete { + UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init]; + + // For testing purposes, you can force a UMPDebugGeography of EEA or not EEA. + UMPDebugSettings *debugSettings = [[UMPDebugSettings alloc] init]; + // debugSettings.geography = UMPDebugGeographyEEA; + parameters.debugSettings = debugSettings; + + // Requesting an update to consent information should be called on every app launch. + [UMPConsentInformation.sharedInstance + requestConsentInfoUpdateWithParameters:parameters + completionHandler:^(NSError *_Nullable requestConsentError) { + if (requestConsentError) { + consentGatheringComplete(requestConsentError); + } else { + [UMPConsentForm + loadAndPresentIfRequiredFromViewController:viewController + completionHandler:^( + NSError + *_Nullable loadAndPresentError) { + // Consent has been gathered. + consentGatheringComplete( + loadAndPresentError); + }]; + } + }]; +} + +- (void)presentPrivacyOptionsFormFromViewController:(UIViewController *)viewController + completionHandler: + (void (^)(NSError *_Nullable))completionHandler { + [UMPConsentForm presentPrivacyOptionsFormFromViewController:viewController + completionHandler:completionHandler]; +} + +@end diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.h b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.h index 0061b9b3..3ea2904c 100644 --- a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.h +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.h @@ -18,16 +18,4 @@ @interface ViewController : UIViewController -/// The game text. -@property(nonatomic, weak) IBOutlet UILabel *gameText; - -/// The play again button. -@property(nonatomic, weak) IBOutlet UIButton *playAgainButton; - -/// The text indicating current coin count. -@property(weak, nonatomic) IBOutlet UILabel *coinCountLabel; - -/// Starts a new game. Shows a rewarded interstitial if it's ready. -- (IBAction)playAgain:(id)sender; - @end diff --git a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.m b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.m index b31d9262..d83a465a 100644 --- a/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.m +++ b/Objective-C/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/ViewController.m @@ -18,6 +18,8 @@ #import +#import "GoogleMobileAdsConsentManager.h" + /// GameState indicates the progress of the game. typedef NS_ENUM(NSUInteger, GameState) { /** Game has not started. */ @@ -38,6 +40,21 @@ typedef NS_ENUM(NSUInteger, GameState) { @interface ViewController () +/// The privacy settings button. +@property(weak, nonatomic) IBOutlet UIBarButtonItem *privacySettingsButton; + +/// The game text. +@property(nonatomic, weak) IBOutlet UILabel *gameText; + +/// The play again button. +@property(nonatomic, weak) IBOutlet UIButton *playAgainButton; + +/// The text indicating current coin count. +@property(weak, nonatomic) IBOutlet UILabel *coinCountLabel; + +/// Starts a new game. Shows a rewarded interstitial if it's ready. +- (IBAction)playAgain:(id)sender; + /// The rewarded interstitial ad. @property(nonatomic) GADRewardedInterstitialAd *rewardedInterstitialAd; @@ -61,27 +78,62 @@ - (void)viewDidLoad { [super viewDidLoad]; // Pause game when application is backgrounded. - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(pauseGame) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(pauseGame) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; // Resume game when application becomes active. - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(resumeGame) - name:UIApplicationDidBecomeActiveNotification - object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(resumeGame) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + + __weak __typeof__(self) weakSelf = self; + [GoogleMobileAdsConsentManager.sharedInstance + gatherConsentFromConsentPresentationViewController:self + consentGatheringComplete:^(NSError *_Nullable consentError) { + __strong __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf startNewGame]; + + if (consentError) { + // Consent gathering failed. + NSLog(@"Error: %@", consentError.localizedDescription); + } + + if (GoogleMobileAdsConsentManager.sharedInstance.canRequestAds) { + [strongSelf startGoogleMobileAdsSDK]; + } + + strongSelf.privacySettingsButton.enabled = + GoogleMobileAdsConsentManager.sharedInstance + .isPrivacyOptionsRequired; + }]; + + // This sample attempts to load ads using consent obtained in the previous session. + if (GoogleMobileAdsConsentManager.sharedInstance.canRequestAds) { + [self startGoogleMobileAdsSDK]; + } +} - [self startNewGame]; +- (void)startGoogleMobileAdsSDK { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Initialize the Google Mobile Ads SDK. + [GADMobileAds.sharedInstance startWithCompletionHandler:nil]; + + // Request an ad. + [self loadRewardedInterstitialAd]; + }); } #pragma mark Game logic - (void)startNewGame { - if (!self.rewardedInterstitialAd) { - [self loadRewardedInterstitialAd]; - } - self.gameState = GameStatePlaying; self.playAgainButton.hidden = YES; self.timeLeft = kGameLength; @@ -217,7 +269,7 @@ - (void)showRewardedInterstitialAd { } } -#pragma GADFullScreeContentDelegate implementation +#pragma mark GADFullScreeContentDelegate implementation - (void)adWillPresentFullScreenContent:(id)ad { NSLog(@"Ad will present full screen content."); @@ -235,10 +287,41 @@ - (void)adDidDismissFullScreenContent:(id)ad { self.playAgainButton.hidden = NO; } -#pragma Rewarded Interstitial button actions +#pragma mark Rewarded Interstitial button actions + +- (IBAction)privacySettingsTapped:(UIBarButtonItem *)sender { + [self pauseGame]; + + [GoogleMobileAdsConsentManager.sharedInstance + presentPrivacyOptionsFormFromViewController:self + completionHandler:^(NSError *_Nullable formError) { + if (formError) { + UIAlertController *alertController = [UIAlertController + alertControllerWithTitle:formError.localizedDescription + message:@"Please try again later." + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *defaultAction = + [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + [self resumeGame]; + }]; + + [alertController addAction:defaultAction]; + [self presentViewController:alertController + animated:YES + completion:nil]; + } else { + [self resumeGame]; + } + }]; +} - (IBAction)playAgain:(id)sender { [self startNewGame]; + if (GoogleMobileAdsConsentManager.sharedInstance.canRequestAds) { + [self loadRewardedInterstitialAd]; + } } @end diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj index 1c5a00e1..df558004 100644 --- a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + A8CC5DFD2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = A8CC5DFC2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.m */; }; CF9F261F1991396300F5C44E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9F261E1991396300F5C44E /* main.m */; }; CF9F26221991396300F5C44E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9F26211991396300F5C44E /* AppDelegate.m */; }; CF9F26251991396300F5C44E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CF9F26241991396300F5C44E /* ViewController.m */; }; @@ -15,6 +16,8 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + A8CC5DFB2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GoogleMobileAdsConsentManager.h; sourceTree = ""; }; + A8CC5DFC2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GoogleMobileAdsConsentManager.m; sourceTree = ""; }; CF9F26191991396300F5C44E /* RewardedInterstitialExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RewardedInterstitialExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; CF9F261D1991396300F5C44E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; CF9F261E1991396300F5C44E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; @@ -66,6 +69,8 @@ children = ( CF9F26201991396300F5C44E /* AppDelegate.h */, CF9F26211991396300F5C44E /* AppDelegate.m */, + A8CC5DFB2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.h */, + A8CC5DFC2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.m */, CF9F26231991396300F5C44E /* ViewController.h */, CF9F26241991396300F5C44E /* ViewController.m */, CF9F26261991396300F5C44E /* Main.storyboard */, @@ -154,6 +159,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A8CC5DFD2A8ECC4A0094B83E /* GoogleMobileAdsConsentManager.m in Sources */, CF9F26251991396300F5C44E /* ViewController.m in Sources */, CF9F26221991396300F5C44E /* AppDelegate.m in Sources */, CF9F261F1991396300F5C44E /* main.m in Sources */, diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.m b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.m index fe55a932..501d77d2 100644 --- a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.m +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.m @@ -16,15 +16,10 @@ #import "AppDelegate.h" -#import - @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Initialize Google Mobile Ads SDK - [GADMobileAds.sharedInstance startWithCompletionHandler:nil]; - return YES; } diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard index e1fe15ca..61bea6eb 100644 --- a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard @@ -1,12 +1,28 @@ - + - + + + + + + + + + + + + + + + + + @@ -36,7 +52,7 @@ @@ -59,10 +75,18 @@ + + + + + + + + diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.h b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.h new file mode 100644 index 00000000..9fb2d8e1 --- /dev/null +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.h @@ -0,0 +1,44 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// The Google Mobile Ads SDK provides the User Messaging Platform (Google's +/// IAB Certified consent management platform) as one solution to capture +/// consent for users in GDPR impacted countries. This is an example and +/// you can choose another consent management platform to capture consent. +@interface GoogleMobileAdsConsentManager : NSObject + +@property(class, atomic, readonly, strong, nonnull) GoogleMobileAdsConsentManager *sharedInstance; +@property(nonatomic, readonly) BOOL canRequestAds; +@property(nonatomic, readonly) BOOL isPrivacyOptionsRequired; + +/// Helper method to call the UMP SDK methods to request consent information and load/present a +/// consent form if necessary. +- (void)gatherConsentFromConsentPresentationViewController:(UIViewController *)viewController + consentGatheringComplete: + (void (^)(NSError *_Nullable error))completionHandler; + +/// Helper method to call the UMP SDK method to present the privacy options form. +- (void)presentPrivacyOptionsFormFromViewController:(UIViewController *)viewController + completionHandler: + (void (^)(NSError *_Nullable error))completionHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.m b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.m new file mode 100644 index 00000000..17686087 --- /dev/null +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.m @@ -0,0 +1,80 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "GoogleMobileAdsConsentManager.h" + +#import +#import + + +@implementation GoogleMobileAdsConsentManager + ++ (instancetype)sharedInstance { + static GoogleMobileAdsConsentManager *shared; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + shared = [[GoogleMobileAdsConsentManager alloc] init]; + }); + return shared; +} + +- (BOOL)canRequestAds { + return UMPConsentInformation.sharedInstance.canRequestAds; +} + +- (BOOL)isPrivacyOptionsRequired { + return UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus == + UMPPrivacyOptionsRequirementStatusRequired; +} + +- (void)gatherConsentFromConsentPresentationViewController:(UIViewController *)viewController + consentGatheringComplete: + (void (^)(NSError *_Nullable))consentGatheringComplete { + UMPRequestParameters *parameters = [[UMPRequestParameters alloc] init]; + + // For testing purposes, you can force a UMPDebugGeography of EEA or not EEA. + UMPDebugSettings *debugSettings = [[UMPDebugSettings alloc] init]; + // debugSettings.geography = UMPDebugGeographyEEA; + parameters.debugSettings = debugSettings; + + // Requesting an update to consent information should be called on every app launch. + [UMPConsentInformation.sharedInstance + requestConsentInfoUpdateWithParameters:parameters + completionHandler:^(NSError *_Nullable requestConsentError) { + if (requestConsentError) { + consentGatheringComplete(requestConsentError); + } else { + [UMPConsentForm + loadAndPresentIfRequiredFromViewController:viewController + completionHandler:^( + NSError + *_Nullable loadAndPresentError) { + // Consent has been gathered. + consentGatheringComplete( + loadAndPresentError); + }]; + } + }]; +} + +- (void)presentPrivacyOptionsFormFromViewController:(UIViewController *)viewController + completionHandler: + (void (^)(NSError *_Nullable))completionHandler { + [UMPConsentForm presentPrivacyOptionsFormFromViewController:viewController + completionHandler:completionHandler]; +} + +@end diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Info.plist b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Info.plist index 70f82d73..7196688d 100644 --- a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Info.plist +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/Info.plist @@ -226,6 +226,8 @@ 3qcr597p9d.skadnetwork + UILaunchStoryboardName + Main.storyboard UIMainStoryboardFile Main UIRequiredDeviceCapabilities diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.h b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.h index 9e88f9bd..7a37d6d5 100644 --- a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.h +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.h @@ -18,16 +18,4 @@ @interface ViewController : UIViewController -/// The game text. -@property(nonatomic, weak) IBOutlet UILabel *gameText; - -/// The play again button. -@property(nonatomic, weak) IBOutlet UIButton *playAgainButton; - -/// The text indicating current coin count. -@property(weak, nonatomic) IBOutlet UILabel *coinCountLabel; - -/// Starts a new game. Shows a rewarded interstitial if it's ready. -- (IBAction)playAgain:(id)sender; - @end diff --git a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.m b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.m index 699172f7..89eaa385 100644 --- a/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.m +++ b/Objective-C/admob/RewardedInterstitialExample/RewardedInterstitialExample/ViewController.m @@ -18,6 +18,8 @@ #import +#import "GoogleMobileAdsConsentManager.h" + /// GameState indicates the progress of the game. typedef NS_ENUM(NSUInteger, GameState) { /** Game has not started. */ @@ -38,6 +40,21 @@ typedef NS_ENUM(NSUInteger, GameState) { @interface ViewController () +/// The privacy settings button. +@property(weak, nonatomic) IBOutlet UIBarButtonItem *privacySettingsButton; + +/// The game text. +@property(nonatomic, weak) IBOutlet UILabel *gameText; + +/// The play again button. +@property(nonatomic, weak) IBOutlet UIButton *playAgainButton; + +/// The text indicating current coin count. +@property(weak, nonatomic) IBOutlet UILabel *coinCountLabel; + +/// Starts a new game. Shows a rewarded interstitial if it's ready. +- (IBAction)playAgain:(id)sender; + /// The rewarded interstitial ad. @property(nonatomic) GADRewardedInterstitialAd *rewardedInterstitialAd; @@ -61,27 +78,62 @@ - (void)viewDidLoad { [super viewDidLoad]; // Pause game when application is backgrounded. - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(pauseGame) - name:UIApplicationDidEnterBackgroundNotification - object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(pauseGame) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; // Resume game when application becomes active. - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(resumeGame) - name:UIApplicationDidBecomeActiveNotification - object:nil]; + [NSNotificationCenter.defaultCenter addObserver:self + selector:@selector(resumeGame) + name:UIApplicationDidBecomeActiveNotification + object:nil]; + + __weak __typeof__(self) weakSelf = self; + [GoogleMobileAdsConsentManager.sharedInstance + gatherConsentFromConsentPresentationViewController:self + consentGatheringComplete:^(NSError *_Nullable consentError) { + __strong __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) { + return; + } + + [strongSelf startNewGame]; + + if (consentError) { + // Consent gathering failed. + NSLog(@"Error: %@", consentError.localizedDescription); + } + + if (GoogleMobileAdsConsentManager.sharedInstance.canRequestAds) { + [strongSelf startGoogleMobileAdsSDK]; + } + + strongSelf.privacySettingsButton.enabled = + GoogleMobileAdsConsentManager.sharedInstance + .isPrivacyOptionsRequired; + }]; + + // This sample attempts to load ads using consent obtained in the previous session. + if (GoogleMobileAdsConsentManager.sharedInstance.canRequestAds) { + [self startGoogleMobileAdsSDK]; + } +} - [self startNewGame]; +- (void)startGoogleMobileAdsSDK { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Initialize the Google Mobile Ads SDK. + [GADMobileAds.sharedInstance startWithCompletionHandler:nil]; + + // Request an ad. + [self loadRewardedInterstitialAd]; + }); } #pragma mark Game logic - (void)startNewGame { - if (!self.rewardedInterstitialAd) { - [self loadRewardedInterstitialAd]; - } - self.gameState = GameStatePlaying; self.playAgainButton.hidden = YES; self.timeLeft = kGameLength; @@ -218,7 +270,7 @@ - (void)showRewardedInterstitialAd { } } -#pragma GADFullScreeContentDelegate implementation +#pragma mark GADFullScreeContentDelegate implementation - (void)adWillPresentFullScreenContent:(id)ad { NSLog(@"Ad did present full screen content."); @@ -236,10 +288,41 @@ - (void)adDidDismissFullScreenContent:(id)ad { self.playAgainButton.hidden = NO; } -#pragma Rewarded Interstitial button actions +#pragma mark Rewarded Interstitial button actions + +- (IBAction)privacySettingsTapped:(UIBarButtonItem *)sender { + [self pauseGame]; + + [GoogleMobileAdsConsentManager.sharedInstance + presentPrivacyOptionsFormFromViewController:self + completionHandler:^(NSError *_Nullable formError) { + if (formError) { + UIAlertController *alertController = [UIAlertController + alertControllerWithTitle:formError.localizedDescription + message:@"Please try again later." + preferredStyle:UIAlertControllerStyleAlert]; + UIAlertAction *defaultAction = + [UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + [self resumeGame]; + }]; + + [alertController addAction:defaultAction]; + [self presentViewController:alertController + animated:YES + completion:nil]; + } else { + [self resumeGame]; + } + }]; +} - (IBAction)playAgain:(id)sender { [self startNewGame]; + if (GoogleMobileAdsConsentManager.sharedInstance.canRequestAds) { + [self loadRewardedInterstitialAd]; + } } @end diff --git a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj index fcbd1c36..19c31ef8 100644 --- a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj +++ b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 849326BB1B066A7F00863759 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 849326B91B066A7F00863759 /* Main.storyboard */; }; 849326BD1B066A7F00863759 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849326BC1B066A7F00863759 /* Images.xcassets */; }; 849326C01B066A7F00863759 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849326BE1B066A7F00863759 /* LaunchScreen.xib */; }; + A8CC5DFA2A8ECA450094B83E /* GoogleMobileAdsConsentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC5DF92A8ECA440094B83E /* GoogleMobileAdsConsentManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -22,6 +23,7 @@ 849326BA1B066A7F00863759 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 849326BC1B066A7F00863759 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 849326BF1B066A7F00863759 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + A8CC5DF92A8ECA440094B83E /* GoogleMobileAdsConsentManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GoogleMobileAdsConsentManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +66,7 @@ children = ( 849326B51B066A7F00863759 /* AppDelegate.swift */, 849326B71B066A7F00863759 /* GameViewController.swift */, + A8CC5DF92A8ECA440094B83E /* GoogleMobileAdsConsentManager.swift */, 849326B91B066A7F00863759 /* Main.storyboard */, 849326BC1B066A7F00863759 /* Images.xcassets */, 849326BE1B066A7F00863759 /* LaunchScreen.xib */, @@ -155,6 +158,7 @@ buildActionMask = 2147483647; files = ( 849326B81B066A7F00863759 /* GameViewController.swift in Sources */, + A8CC5DFA2A8ECA450094B83E /* GoogleMobileAdsConsentManager.swift in Sources */, 849326B61B066A7F00863759 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.swift b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.swift index 771eed1b..279ddb18 100644 --- a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.swift +++ b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/AppDelegate.swift @@ -1,5 +1,5 @@ // -// Copyright (C) 2023 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,10 +26,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - - // Initialize Google Mobile Ads SDK - GADMobileAds.sharedInstance().start() - return true } diff --git a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard index 9055a384..0bdfbae5 100644 --- a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard +++ b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/Base.lproj/Main.storyboard @@ -1,30 +1,48 @@ - + + - + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + diff --git a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GameViewController.swift b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GameViewController.swift index 0cfa4518..8a131d0d 100644 --- a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GameViewController.swift +++ b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GameViewController.swift @@ -1,5 +1,5 @@ // -// Copyright (C) 2023 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -47,6 +47,12 @@ class GameViewController: UIViewController, GADFullScreenContentDelegate { /// The state of the game. private var gameState = GameState.notStarted + /// Indicates whether the Google Mobile Ads SDK has started. + private var isMobileAdsStartCalled = false + + /// The privacy settings button. + @IBOutlet weak var privacySettingsButton: UIBarButtonItem! + /// The countdown timer label. @IBOutlet weak var gameText: UILabel! @@ -71,16 +77,46 @@ class GameViewController: UIViewController, GADFullScreenContentDelegate { selector: #selector(GameViewController.resumeGame), name: UIApplication.didBecomeActiveNotification, object: nil) - startNewGame() + GoogleMobileAdsConsentManager.shared.gatherConsent(from: self) { [weak self] consentError in + guard let self else { return } + + self.startNewGame() + + if let consentError { + // Consent gathering failed. + print("Error: \(consentError.localizedDescription)") + } + + if GoogleMobileAdsConsentManager.shared.canRequestAds { + self.startGoogleMobileAdsSDK() + } + + self.privacySettingsButton.isEnabled = + GoogleMobileAdsConsentManager.shared.isPrivacyOptionsRequired + } + + // This sample attempts to load ads using consent obtained in the previous session. + if GoogleMobileAdsConsentManager.shared.canRequestAds { + startGoogleMobileAdsSDK() + } } - // MARK: - Game Logic + private func startGoogleMobileAdsSDK() { + DispatchQueue.main.async { + guard !self.isMobileAdsStartCalled else { return } - private func startNewGame() { - if rewardedInterstitialAd == nil { - loadRewardedInterstitialAd() + self.isMobileAdsStartCalled = true + + // Initialize the Google Mobile Ads SDK. + GADMobileAds.sharedInstance().start() + // Request an ad. + self.loadRewardedInterstitialAd() } + } + + // MARK: - Game Logic + private func startNewGame() { gameState = .playing timeLeft = GameViewController.gameLength playAgainButton.isHidden = true @@ -198,10 +234,34 @@ class GameViewController: UIViewController, GADFullScreenContentDelegate { } } - // MARK: - Interstitial Button Actions + // MARK: - Button Actions + + @IBAction func privacySettingsTapped(_ sender: UIBarButtonItem) { + pauseGame() + + GoogleMobileAdsConsentManager.shared.presentPrivacyOptionsForm(from: self) { + [weak self] formError in + guard let self else { return } + guard let formError else { return self.resumeGame() } + + let alertController = UIAlertController( + title: formError.localizedDescription, message: "Please try again later.", + preferredStyle: .alert) + alertController.addAction( + UIAlertAction( + title: "OK", style: .cancel, + handler: { _ in + self.resumeGame() + })) + self.present(alertController, animated: true) + } + } @IBAction func playAgain(_ sender: AnyObject) { startNewGame() + if GoogleMobileAdsConsentManager.shared.canRequestAds { + loadRewardedInterstitialAd() + } } // MARK: - GADFullScreenContentDelegate diff --git a/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.swift b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.swift new file mode 100644 index 00000000..037fe058 --- /dev/null +++ b/Swift/admanager/AdManagerRewardedInterstitialExample/AdManagerRewardedInterstitialExample/GoogleMobileAdsConsentManager.swift @@ -0,0 +1,73 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import GoogleMobileAds +import UserMessagingPlatform + +/// The Google Mobile Ads SDK provides the User Messaging Platform (Google's +/// IAB Certified consent management platform) as one solution to capture +/// consent for users in GDPR impacted countries. This is an example and +/// you can choose another consent management platform to capture consent. + +class GoogleMobileAdsConsentManager: NSObject { + static let shared = GoogleMobileAdsConsentManager() + + var canRequestAds: Bool { + return UMPConsentInformation.sharedInstance.canRequestAds + } + + var isPrivacyOptionsRequired: Bool { + return UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus == .required + } + + /// Helper method to call the UMP SDK methods to request consent information and load/present a + /// consent form if necessary. + func gatherConsent( + from consentFormPresentationviewController: UIViewController, + consentGatheringComplete: @escaping (Error?) -> Void + ) { + let parameters = UMPRequestParameters() + + //For testing purposes, you can force a UMPDebugGeography of EEA or not EEA. + let debugSettings = UMPDebugSettings() + // debugSettings.geography = UMPDebugGeography.EEA + parameters.debugSettings = debugSettings + + // Requesting an update to consent information should be called on every app launch. + UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: parameters) { + requestConsentError in + guard requestConsentError == nil else { + return consentGatheringComplete(requestConsentError) + } + + UMPConsentForm.loadAndPresentIfRequired(from: consentFormPresentationviewController) { + loadAndPresentError in + + // Consent has been gathered. + consentGatheringComplete(loadAndPresentError) + } + } + } + + /// Helper method to call the UMP SDK method to present the privacy options form. + func presentPrivacyOptionsForm( + from viewController: UIViewController, completionHandler: @escaping (Error?) -> Void + ) { + UMPConsentForm.presentPrivacyOptionsForm( + from: viewController, completionHandler: completionHandler) + } +} diff --git a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj index a6ebea8d..717a6966 100644 --- a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj +++ b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 849326BB1B066A7F00863759 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 849326B91B066A7F00863759 /* Main.storyboard */; }; 849326BD1B066A7F00863759 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 849326BC1B066A7F00863759 /* Images.xcassets */; }; 849326C01B066A7F00863759 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 849326BE1B066A7F00863759 /* LaunchScreen.xib */; }; + A8CC5DF82A8EC6E60094B83E /* GoogleMobileAdsConsentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8CC5DF72A8EC6E60094B83E /* GoogleMobileAdsConsentManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -22,6 +23,7 @@ 849326BA1B066A7F00863759 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 849326BC1B066A7F00863759 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 849326BF1B066A7F00863759 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + A8CC5DF72A8EC6E60094B83E /* GoogleMobileAdsConsentManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GoogleMobileAdsConsentManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -64,6 +66,7 @@ children = ( 849326B51B066A7F00863759 /* AppDelegate.swift */, 849326B71B066A7F00863759 /* GameViewController.swift */, + A8CC5DF72A8EC6E60094B83E /* GoogleMobileAdsConsentManager.swift */, 849326B91B066A7F00863759 /* Main.storyboard */, 849326BC1B066A7F00863759 /* Images.xcassets */, 849326BE1B066A7F00863759 /* LaunchScreen.xib */, @@ -155,6 +158,7 @@ buildActionMask = 2147483647; files = ( 849326B81B066A7F00863759 /* GameViewController.swift in Sources */, + A8CC5DF82A8EC6E60094B83E /* GoogleMobileAdsConsentManager.swift in Sources */, 849326B61B066A7F00863759 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.swift b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.swift index 2959860f..8477ec68 100644 --- a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.swift +++ b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/AppDelegate.swift @@ -1,5 +1,5 @@ // -// Copyright (C) 2022 Google, Inc. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -26,10 +26,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - - // Initialize Google Mobile Ads SDK - GADMobileAds.sharedInstance().start() - return true } diff --git a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard index 9055a384..f6c8e1a8 100644 --- a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard +++ b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/Base.lproj/Main.storyboard @@ -1,10 +1,28 @@ - + + - + + + + + + + + + + + + + + + + + + @@ -14,17 +32,17 @@ - + + + + + + + + + diff --git a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GameViewController.swift b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GameViewController.swift index 899e7c14..bc26b78e 100644 --- a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GameViewController.swift +++ b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GameViewController.swift @@ -1,5 +1,5 @@ // -// Copyright (C) 2022 Google, Inc. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -47,6 +47,12 @@ class GameViewController: UIViewController, GADFullScreenContentDelegate { /// The state of the game. private var gameState = GameState.notStarted + /// Indicates whether the Google Mobile Ads SDK has started. + private var isMobileAdsStartCalled = false + + /// The privacy settings button. + @IBOutlet weak var privacySettingsButton: UIBarButtonItem! + /// The countdown timer label. @IBOutlet weak var gameText: UILabel! @@ -71,16 +77,46 @@ class GameViewController: UIViewController, GADFullScreenContentDelegate { selector: #selector(GameViewController.resumeGame), name: UIApplication.didBecomeActiveNotification, object: nil) - startNewGame() + GoogleMobileAdsConsentManager.shared.gatherConsent(from: self) { [weak self] consentError in + guard let self else { return } + + self.startNewGame() + + if let consentError { + // Consent gathering failed. + print("Error: \(consentError.localizedDescription)") + } + + if GoogleMobileAdsConsentManager.shared.canRequestAds { + self.startGoogleMobileAdsSDK() + } + + self.privacySettingsButton.isEnabled = + GoogleMobileAdsConsentManager.shared.isPrivacyOptionsRequired + } + + // This sample attempts to load ads using consent obtained in the previous session. + if GoogleMobileAdsConsentManager.shared.canRequestAds { + startGoogleMobileAdsSDK() + } } - // MARK: - Game Logic + private func startGoogleMobileAdsSDK() { + DispatchQueue.main.async { + guard !self.isMobileAdsStartCalled else { return } - private func startNewGame() { - if rewardedInterstitialAd == nil { - loadRewardedInterstitialAd() + self.isMobileAdsStartCalled = true + + // Initialize the Google Mobile Ads SDK. + GADMobileAds.sharedInstance().start() + // Request an ad. + self.loadRewardedInterstitialAd() } + } + + // MARK: - Game Logic + private func startNewGame() { gameState = .playing timeLeft = GameViewController.gameLength playAgainButton.isHidden = true @@ -198,10 +234,34 @@ class GameViewController: UIViewController, GADFullScreenContentDelegate { } } - // MARK: - Interstitial Button Actions + // MARK: - Button Actions + + @IBAction func privacySettingsTapped(_ sender: UIBarButtonItem) { + pauseGame() + + GoogleMobileAdsConsentManager.shared.presentPrivacyOptionsForm(from: self) { + [weak self] formError in + guard let self else { return } + guard let formError else { return self.resumeGame() } + + let alertController = UIAlertController( + title: formError.localizedDescription, message: "Please try again later.", + preferredStyle: .alert) + alertController.addAction( + UIAlertAction( + title: "OK", style: .cancel, + handler: { _ in + self.resumeGame() + })) + self.present(alertController, animated: true) + } + } @IBAction func playAgain(_ sender: AnyObject) { startNewGame() + if GoogleMobileAdsConsentManager.shared.canRequestAds { + loadRewardedInterstitialAd() + } } // MARK: - GADFullScreenContentDelegate diff --git a/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.swift b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.swift new file mode 100644 index 00000000..037fe058 --- /dev/null +++ b/Swift/admob/RewardedInterstitialExample/RewardedInterstitialExample/GoogleMobileAdsConsentManager.swift @@ -0,0 +1,73 @@ +// +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import GoogleMobileAds +import UserMessagingPlatform + +/// The Google Mobile Ads SDK provides the User Messaging Platform (Google's +/// IAB Certified consent management platform) as one solution to capture +/// consent for users in GDPR impacted countries. This is an example and +/// you can choose another consent management platform to capture consent. + +class GoogleMobileAdsConsentManager: NSObject { + static let shared = GoogleMobileAdsConsentManager() + + var canRequestAds: Bool { + return UMPConsentInformation.sharedInstance.canRequestAds + } + + var isPrivacyOptionsRequired: Bool { + return UMPConsentInformation.sharedInstance.privacyOptionsRequirementStatus == .required + } + + /// Helper method to call the UMP SDK methods to request consent information and load/present a + /// consent form if necessary. + func gatherConsent( + from consentFormPresentationviewController: UIViewController, + consentGatheringComplete: @escaping (Error?) -> Void + ) { + let parameters = UMPRequestParameters() + + //For testing purposes, you can force a UMPDebugGeography of EEA or not EEA. + let debugSettings = UMPDebugSettings() + // debugSettings.geography = UMPDebugGeography.EEA + parameters.debugSettings = debugSettings + + // Requesting an update to consent information should be called on every app launch. + UMPConsentInformation.sharedInstance.requestConsentInfoUpdate(with: parameters) { + requestConsentError in + guard requestConsentError == nil else { + return consentGatheringComplete(requestConsentError) + } + + UMPConsentForm.loadAndPresentIfRequired(from: consentFormPresentationviewController) { + loadAndPresentError in + + // Consent has been gathered. + consentGatheringComplete(loadAndPresentError) + } + } + } + + /// Helper method to call the UMP SDK method to present the privacy options form. + func presentPrivacyOptionsForm( + from viewController: UIViewController, completionHandler: @escaping (Error?) -> Void + ) { + UMPConsentForm.presentPrivacyOptionsForm( + from: viewController, completionHandler: completionHandler) + } +}