diff --git a/demo-ios-objc/Podfile b/demo-ios-objc/Podfile index 71f6808..c1c585c 100644 --- a/demo-ios-objc/Podfile +++ b/demo-ios-objc/Podfile @@ -12,7 +12,8 @@ target 'demo-ios-objc' do #pod 'OptableSDK' pod 'Google-Mobile-Ads-SDK' - + pod 'PrebidMobile' + target 'demo-ios-objcTests' do inherit! :search_paths # Pods for testing diff --git a/demo-ios-objc/Podfile.lock b/demo-ios-objc/Podfile.lock index 2fb5c4b..ec32d5a 100644 --- a/demo-ios-objc/Podfile.lock +++ b/demo-ios-objc/Podfile.lock @@ -3,15 +3,20 @@ PODS: - GoogleUserMessagingPlatform (>= 1.1) - GoogleUserMessagingPlatform (3.0.0) - OptableSDK (0.10.0) + - PrebidMobile (3.1.0): + - PrebidMobile/core (= 3.1.0) + - PrebidMobile/core (3.1.0) DEPENDENCIES: - Google-Mobile-Ads-SDK - OptableSDK (from `../`) + - PrebidMobile SPEC REPOS: trunk: - Google-Mobile-Ads-SDK - GoogleUserMessagingPlatform + - PrebidMobile EXTERNAL SOURCES: OptableSDK: @@ -21,7 +26,8 @@ SPEC CHECKSUMS: Google-Mobile-Ads-SDK: 4dde70a8c18d96b14f9548759b8cec6ecb0bc3e6 GoogleUserMessagingPlatform: f8d0cdad3ca835406755d0a69aa634f00e76d576 OptableSDK: fc5d3852c29fac1881b1d3ab6ea397de71c8cbf1 + PrebidMobile: 046bb6220157c7332dc6c6e19a99397bb481ac3a -PODFILE CHECKSUM: 48d927338b39550c29272b694f6c18710f33f913 +PODFILE CHECKSUM: 5946bfde95ae1dcf09c2ab56c00445beb6cd26c2 COCOAPODS: 1.16.2 diff --git a/demo-ios-objc/demo-ios-objc.xcodeproj/project.pbxproj b/demo-ios-objc/demo-ios-objc.xcodeproj/project.pbxproj index c53bf04..ece18cb 100644 --- a/demo-ios-objc/demo-ios-objc.xcodeproj/project.pbxproj +++ b/demo-ios-objc/demo-ios-objc.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 10F4BF742EFDE899E4AE482F /* Pods_demo_ios_objc.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 35D48072B3533132ACEA0D21 /* Pods_demo_ios_objc.framework */; }; 11D09485910F1A1EB9871E8E /* Pods_demo_ios_objcTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB5262E52B5F4397F9533E61 /* Pods_demo_ios_objcTests.framework */; }; + 536D9E992EA0EE06006D86BE /* PrebidBannerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 536D9E982EA0EE06006D86BE /* PrebidBannerViewController.m */; }; 6320EEFE2535F92300F76877 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6320EEFD2535F92300F76877 /* AppDelegate.m */; }; 6320EF012535F92300F76877 /* SceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6320EF002535F92300F76877 /* SceneDelegate.m */; }; 6320EF042535F92300F76877 /* IdentifyViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6320EF032535F92300F76877 /* IdentifyViewController.m */; }; @@ -47,6 +48,8 @@ 35D48072B3533132ACEA0D21 /* Pods_demo_ios_objc.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_demo_ios_objc.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45728C7558D469A46F00466E /* Pods-demo-ios-objc-demo-ios-objcUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-demo-ios-objc-demo-ios-objcUITests.debug.xcconfig"; path = "Target Support Files/Pods-demo-ios-objc-demo-ios-objcUITests/Pods-demo-ios-objc-demo-ios-objcUITests.debug.xcconfig"; sourceTree = ""; }; 523A0EFF3A972FC18833C15D /* Pods-demo-ios-objc-demo-ios-objcUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-demo-ios-objc-demo-ios-objcUITests.release.xcconfig"; path = "Target Support Files/Pods-demo-ios-objc-demo-ios-objcUITests/Pods-demo-ios-objc-demo-ios-objcUITests.release.xcconfig"; sourceTree = ""; }; + 536D9E972EA0EE06006D86BE /* PrebidBannerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrebidBannerViewController.h; sourceTree = ""; }; + 536D9E982EA0EE06006D86BE /* PrebidBannerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PrebidBannerViewController.m; sourceTree = ""; }; 5C562AA9ABB1162DB4C0814E /* Pods_demo_ios_objc_demo_ios_objcUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_demo_ios_objc_demo_ios_objcUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6320EEF92535F92300F76877 /* demo-ios-objc.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "demo-ios-objc.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 6320EEFC2535F92300F76877 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -149,6 +152,8 @@ 6320EF032535F92300F76877 /* IdentifyViewController.m */, 63B5A8C52536704F000CA436 /* GAMBannerViewController.h */, 63B5A8C025366FE8000CA436 /* GAMBannerViewController.m */, + 536D9E972EA0EE06006D86BE /* PrebidBannerViewController.h */, + 536D9E982EA0EE06006D86BE /* PrebidBannerViewController.m */, 6320EF052535F92300F76877 /* Main.storyboard */, 6320EF082535F92500F76877 /* Assets.xcassets */, 6320EF0A2535F92500F76877 /* LaunchScreen.storyboard */, @@ -479,6 +484,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 536D9E992EA0EE06006D86BE /* PrebidBannerViewController.m in Sources */, 63B5A91E2536825A000CA436 /* IdentifyViewController.h in Sources */, 6320EF042535F92300F76877 /* IdentifyViewController.m in Sources */, 63B5A91A25368252000CA436 /* GAMBannerViewController.h in Sources */, diff --git a/demo-ios-objc/demo-ios-objc/AppDelegate.m b/demo-ios-objc/demo-ios-objc/AppDelegate.m index 3a90b4b..ea08342 100644 --- a/demo-ios-objc/demo-ios-objc/AppDelegate.m +++ b/demo-ios-objc/demo-ios-objc/AppDelegate.m @@ -8,8 +8,12 @@ #import "AppDelegate.h" #import "OptableSDKDelegate.h" + @import OptableSDK; +@import PrebidMobile; +@import GoogleMobileAds; + OptableSDK *OPTABLE = nil; @interface AppDelegate () @@ -18,13 +22,31 @@ @interface AppDelegate () @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - // Override point for customization after application launch. + [self initOptable]; + + [self initPrebidMobile]; + [self initGoogleMobileAds]; + + return YES; +} + +- (void)initOptable { OPTABLE = [[OptableSDK alloc] initWithHost: @"sandbox.optable.co" app: @"ios-sdk-demo" insecure: NO useragent: nil]; OptableSDKDelegate *delegate = [[OptableSDKDelegate alloc] init]; OPTABLE.delegate = delegate; +} - return YES; +- (void)initPrebidMobile { + Prebid.shared.prebidServerAccountId = @"0689a263-318d-448b-a3d4-b02e8a709d9d"; + + [Prebid initializeSDKWithServerURL:@"https://prebid-server-test-j.prebid.org/openrtb2/auction" + error:nil + :nil]; +} + +- (void)initGoogleMobileAds { + [[GADMobileAds sharedInstance] startWithCompletionHandler:^(GADInitializationStatus * _Nonnull status) {}]; } #pragma mark - UISceneSession lifecycle diff --git a/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard b/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard index 464d7c2..c3e01bf 100644 --- a/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard +++ b/demo-ios-objc/demo-ios-objc/Base.lproj/Main.storyboard @@ -204,6 +204,7 @@ + @@ -248,6 +249,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -259,7 +368,7 @@ - + diff --git a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m index fd27a96..859f174 100644 --- a/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m +++ b/demo-ios-objc/demo-ios-objc/GAMBannerViewController.m @@ -20,16 +20,20 @@ @interface GAMBannerViewController () @implementation GAMBannerViewController +- (NSString *)AD_MANAGER_AD_UNIT_ID { + return @"/22081946781/ios-sdk-demo/mobile-leaderboard"; +} + - (void)viewDidLoad { [super viewDidLoad]; self.bannerView = [[GADBannerView alloc] initWithAdSize:GADAdSizeBanner]; - self.bannerView.adUnitID = @"/22081946781/ios-sdk-demo/mobile-leaderboard"; - [self addBannerViewToView:self.bannerView]; + self.bannerView.adUnitID = self.AD_MANAGER_AD_UNIT_ID; self.bannerView.rootViewController = self; + [self addBannerViewToView:self.bannerView]; OptableSDKDelegate *delegate = (OptableSDKDelegate *)OPTABLE.delegate; - delegate.bannerView = self.bannerView; + delegate.adManagerBannerView = self.bannerView; delegate.targetingOutput = self.targetingOutput; } @@ -48,16 +52,16 @@ - (IBAction)loadBannerWithTargetingFromCache:(id)sender { GAMRequest *request = [GAMRequest request]; NSDictionary *keyvals = nil; - [_targetingOutput setText:@"Checking local targeting cache...\n\n"]; + [_targetingOutput setText:@"๐Ÿ—‚ Checking local targeting cache...\n\n"]; keyvals = [OPTABLE targetingFromCache]; if (keyvals != nil) { request.customTargeting = keyvals; NSLog(@"[OptableSDK] Cached targeting values found: %@", keyvals); - [_targetingOutput setText:[NSString stringWithFormat:@"%@\nFound cached data: %@\n", [_targetingOutput text], keyvals]]; + [_targetingOutput setText:[NSString stringWithFormat:@"%@\nโœ… Found cached data: %@\n", [_targetingOutput text], keyvals]]; } else { - [_targetingOutput setText:[NSString stringWithFormat:@"%@\nCache empty.\n", + [_targetingOutput setText:[NSString stringWithFormat:@"%@\nโ„น๏ธ Cache empty.\n", [_targetingOutput text]]]; } @@ -71,6 +75,8 @@ - (IBAction)clearTargetingCache:(id)sender { [OPTABLE targetingClearCache]; } +// MARK: - Helpers + - (void)addBannerViewToView:(UIView *)bannerView { bannerView.translatesAutoresizingMaskIntoConstraints = NO; [self.adPlaceholder addSubview:bannerView]; diff --git a/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.h b/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.h index ae1a2f1..a97ac54 100644 --- a/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.h +++ b/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.h @@ -7,11 +7,19 @@ // @import OptableSDK; + +@import PrebidMobile; @import GoogleMobileAds; @interface OptableSDKDelegate: NSObject -@property(atomic, readwrite, strong) GADBannerView *bannerView; +// MARK: - PrebidMobile +@property(atomic, readwrite, weak) BannerAdUnit *pbmBannerAdUnit; + +// MARK: - GoogleMobileAds +@property(atomic, readwrite, weak) GADBannerView *adManagerBannerView; + +// MARK: - Text Output @property(atomic, readwrite, strong) UITextView *identifyOutput; @property(atomic, readwrite, strong) UITextView *targetingOutput; diff --git a/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.m b/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.m index 67e0774..7f8d41e 100644 --- a/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.m +++ b/demo-ios-objc/demo-ios-objc/OptableSDKDelegate.m @@ -44,17 +44,17 @@ - (void)targetingOk:(NSDictionary *)result { // Update the GAM banner view with result targeting keyvalues: GAMRequest *request = [GAMRequest request]; request.customTargeting = result; - [self.bannerView loadRequest:request]; + [self.adManagerBannerView loadRequest:request]; NSLog(@"[OptableSDK] Success on /targeting API call: %@", result); dispatch_async(dispatch_get_main_queue(), ^{ - [self.targetingOutput setText:[NSString stringWithFormat:@"%@\nData: %@\n", [self.targetingOutput text], result]]; + [self.targetingOutput setText:[NSString stringWithFormat:@"%@\nโœ… Data: %@\n", [self.targetingOutput text], result]]; }); } - (void)targetingErr:(NSError *)error { // Update the GAM banner view without targeting data: GAMRequest *request = [GAMRequest request]; - [self.bannerView loadRequest:request]; + [self.adManagerBannerView loadRequest:request]; NSLog(@"[OptableSDK] Error on /targeting API call: %@", [error localizedDescription]); dispatch_async(dispatch_get_main_queue(), ^{ @@ -75,4 +75,29 @@ - (void)witnessErr:(NSError *)error { [self.targetingOutput setText:[NSString stringWithFormat:@"%@\n๐Ÿšซ Error: %@\n", [self.targetingOutput text], [error localizedDescription]]]; }); } + +- (void)loadBannerWithKeyValues:(NSDictionary * _Nullable)keyValues { + GAMRequest *request = [GAMRequest request]; + + if (self.pbmBannerAdUnit) { + __weak typeof(self) weakSelf = self; + [self.pbmBannerAdUnit fetchDemandWithAdObject:request completion:^(enum ResultCode status) { + if (status != ResultCodePrebidDemandFetchSuccess) { + NSLog(@"[PrebidMobile SDK] Prebid fetch demand failed: %ld", (long)status); + } + if (keyValues.count > 0) { + NSMutableDictionary *merged = [request.customTargeting mutableCopy] ?: [NSMutableDictionary dictionary]; + [merged addEntriesFromDictionary:keyValues]; + request.customTargeting = merged; + } + [weakSelf.adManagerBannerView loadRequest:request]; + }]; + } else { + if (keyValues.count > 0) { + request.customTargeting = keyValues; + } + [self.adManagerBannerView loadRequest:request]; + } +} + @end diff --git a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.h b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.h new file mode 100644 index 0000000..b2ece55 --- /dev/null +++ b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.h @@ -0,0 +1,22 @@ +// +// PrebidBannerViewController.h +// demo-ios-objc +// +// Copyright ยฉ 2020 Optable Technologies Inc. All rights reserved. +// See LICENSE for details. +// + +#import + +@interface PrebidBannerViewController : UIViewController + +@property (weak, nonatomic) IBOutlet UIView *adPlaceholder; + +@property (weak, nonatomic) IBOutlet UIButton *loadBannerButton; +@property (weak, nonatomic) IBOutlet UIButton *cachedBannerButton; +@property (weak, nonatomic) IBOutlet UIButton *clearTargetingCacheButton; +@property (weak, nonatomic) IBOutlet UITextView *targetingOutput; + +- (IBAction)loadBannerWithTargeting:(id)sender; + +@end diff --git a/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m new file mode 100644 index 0000000..001c860 --- /dev/null +++ b/demo-ios-objc/demo-ios-objc/PrebidBannerViewController.m @@ -0,0 +1,100 @@ +// +// PrebidBannerViewController.m +// demo-ios-objc +// +// Copyright ยฉ 2020 Optable Technologies Inc. All rights reserved. +// See LICENSE for details. +// + +#import "OptableSDKDelegate.h" +#import "PrebidBannerViewController.h" +#import "AppDelegate.h" + +@import OptableSDK; +@import GoogleMobileAds; + +@interface PrebidBannerViewController () + +@property(nonatomic, strong) GADBannerView *bannerView; +@property(nonatomic, strong) BannerAdUnit *pbmBannerAdUnit; + +@end + +@implementation PrebidBannerViewController + +- (NSString *)AD_MANAGER_AD_UNIT_ID { + return @"/21808260008/prebid_demo_app_original_api_banner"; +} + +- (NSString *)PREBID_STORED_IMP { + return @"prebid-demo-banner-320-50"; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.bannerView = [[GADBannerView alloc] initWithAdSize:GADAdSizeBanner]; + self.bannerView.adUnitID = self.AD_MANAGER_AD_UNIT_ID; + self.bannerView.rootViewController = self; + [self addBannerViewToView:self.bannerView]; + + self.pbmBannerAdUnit = [[BannerAdUnit alloc] initWithConfigId:self.PREBID_STORED_IMP + size:CGSizeMake(320, 50)]; + + OptableSDKDelegate *delegate = (OptableSDKDelegate *)OPTABLE.delegate; + delegate.adManagerBannerView = self.bannerView; + delegate.pbmBannerAdUnit = self.pbmBannerAdUnit; + delegate.targetingOutput = self.targetingOutput; +} + +- (IBAction)loadBannerWithTargeting:(id)sender { + NSError *error = nil; + + [_targetingOutput setText:@"๐Ÿ“ก Calling /targeting API...\n"]; + + [OPTABLE targetingAndReturnError:&error]; + [OPTABLE witness:@"PrebidBannerViewController.loadBannerClicked" properties:@{ @"example": @"value" } error:&error]; + [OPTABLE profileWithTraits:@{ @"example": @"value", @"anotherExample": @123, @"thirdExample": @YES } error:&error]; +} + +- (IBAction)loadBannerWithTargetingFromCache:(id)sender { + NSError *error = nil; + GAMRequest *request = [GAMRequest request]; + NSDictionary *keyvals = nil; + + [_targetingOutput setText:@"๐Ÿ—‚ Checking local targeting cache...\n\n"]; + + keyvals = [OPTABLE targetingFromCache]; + + if (keyvals != nil) { + request.customTargeting = keyvals; + NSLog(@"[OptableSDK] Cached targeting values found: %@", keyvals); + [_targetingOutput setText:[NSString stringWithFormat:@"%@\nโœ… Found cached data: %@\n", [_targetingOutput text], keyvals]]; + } else { + [_targetingOutput setText:[NSString stringWithFormat:@"%@\nโ„น๏ธ Cache empty.\n", + [_targetingOutput text]]]; + } + + [self.bannerView loadRequest:request]; + [OPTABLE witness:@"PrebidBannerViewController.loadBannerClicked" properties:@{ @"example": @"value" } error:&error]; + [OPTABLE profileWithTraits:@{ @"example": @"value", @"anotherExample": @123, @"thirdExample": @YES } error:&error]; +} + +- (IBAction)clearTargetingCache:(id)sender { + [_targetingOutput setText:@"๐Ÿงน Clearing local targeting cache.\n"]; + [OPTABLE targetingClearCache]; +} + +// MARK: - Helpers + +- (void)addBannerViewToView:(UIView *)bannerView { + bannerView.translatesAutoresizingMaskIntoConstraints = NO; + [self.adPlaceholder addSubview:bannerView]; + + [NSLayoutConstraint activateConstraints:@[ + [bannerView.centerXAnchor constraintEqualToAnchor:self.adPlaceholder.centerXAnchor], + [bannerView.centerYAnchor constraintEqualToAnchor:self.adPlaceholder.centerYAnchor] + ]]; +} + +@end diff --git a/demo-ios-swift/Podfile b/demo-ios-swift/Podfile index 15aa921..ac9014e 100644 --- a/demo-ios-swift/Podfile +++ b/demo-ios-swift/Podfile @@ -12,6 +12,7 @@ target 'demo-ios-swift' do #pod 'OptableSDK' pod 'Google-Mobile-Ads-SDK' + pod 'PrebidMobile' target 'demo-ios-swiftTests' do inherit! :search_paths diff --git a/demo-ios-swift/Podfile.lock b/demo-ios-swift/Podfile.lock index e77c5d6..1b280f7 100644 --- a/demo-ios-swift/Podfile.lock +++ b/demo-ios-swift/Podfile.lock @@ -3,15 +3,20 @@ PODS: - GoogleUserMessagingPlatform (>= 1.1) - GoogleUserMessagingPlatform (3.0.0) - OptableSDK (0.10.0) + - PrebidMobile (3.1.0): + - PrebidMobile/core (= 3.1.0) + - PrebidMobile/core (3.1.0) DEPENDENCIES: - Google-Mobile-Ads-SDK - OptableSDK (from `../`) + - PrebidMobile SPEC REPOS: trunk: - Google-Mobile-Ads-SDK - GoogleUserMessagingPlatform + - PrebidMobile EXTERNAL SOURCES: OptableSDK: @@ -21,7 +26,8 @@ SPEC CHECKSUMS: Google-Mobile-Ads-SDK: b833c723759e32bbaf06edaaf2293f08ed898232 GoogleUserMessagingPlatform: f8d0cdad3ca835406755d0a69aa634f00e76d576 OptableSDK: fc5d3852c29fac1881b1d3ab6ea397de71c8cbf1 + PrebidMobile: 046bb6220157c7332dc6c6e19a99397bb481ac3a -PODFILE CHECKSUM: a7bf67fad0ec61ca3a95f0f8a9aadf2c4fd2cd76 +PODFILE CHECKSUM: 4850ce746afe724d281b9ba584852fa57b0ff0fa COCOAPODS: 1.16.2 diff --git a/demo-ios-swift/demo-ios-swift.xcodeproj/project.pbxproj b/demo-ios-swift/demo-ios-swift.xcodeproj/project.pbxproj index f4e17c3..7a4d6b5 100644 --- a/demo-ios-swift/demo-ios-swift.xcodeproj/project.pbxproj +++ b/demo-ios-swift/demo-ios-swift.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 3E8C4D28A821E9F0A202EA9D /* Pods_demo_ios_swift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D76144A328FE0069ABDF4B5F /* Pods_demo_ios_swift.framework */; }; + 536D9E922EA0DD37006D86BE /* PrebidBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536D9E912EA0DD37006D86BE /* PrebidBannerViewController.swift */; }; 631466ED24F7F555007DCA5D /* GAMBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 631466EC24F7F555007DCA5D /* GAMBannerViewController.swift */; }; 6352AA5B24DC7AE9002E66EB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6352AA5A24DC7AE9002E66EB /* AppDelegate.swift */; }; 6352AA5D24DC7AE9002E66EB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6352AA5C24DC7AE9002E66EB /* SceneDelegate.swift */; }; @@ -54,6 +55,7 @@ /* Begin PBXFileReference section */ 0A022CEF25F2CA644724048D /* Pods_demo_ios_swiftTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_demo_ios_swiftTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3386157F1FDF211F3621C6CF /* Pods-demo-ios-swift-demo-ios-swiftUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-demo-ios-swift-demo-ios-swiftUITests.debug.xcconfig"; path = "Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests.debug.xcconfig"; sourceTree = ""; }; + 536D9E912EA0DD37006D86BE /* PrebidBannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrebidBannerViewController.swift; sourceTree = ""; }; 631466EC24F7F555007DCA5D /* GAMBannerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GAMBannerViewController.swift; sourceTree = ""; }; 6352AA5724DC7AE9002E66EB /* demo-ios-swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "demo-ios-swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 6352AA5A24DC7AE9002E66EB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -134,6 +136,7 @@ 6352AA5A24DC7AE9002E66EB /* AppDelegate.swift */, 6352AA5C24DC7AE9002E66EB /* SceneDelegate.swift */, 631466EC24F7F555007DCA5D /* GAMBannerViewController.swift */, + 536D9E912EA0DD37006D86BE /* PrebidBannerViewController.swift */, 6352AA5E24DC7AE9002E66EB /* IdentifyViewController.swift */, 6352AA6024DC7AE9002E66EB /* Main.storyboard */, 6352AA6324DC7AEC002E66EB /* Assets.xcassets */, @@ -393,10 +396,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests-frameworks.sh\"\n"; @@ -410,10 +417,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift/Pods-demo-ios-swift-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift/Pods-demo-ios-swift-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift/Pods-demo-ios-swift-frameworks.sh\"\n"; @@ -427,10 +438,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift-demo-ios-swiftUITests/Pods-demo-ios-swift-demo-ios-swiftUITests-resources.sh\"\n"; @@ -444,10 +459,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift/Pods-demo-ios-swift-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift/Pods-demo-ios-swift-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-demo-ios-swift/Pods-demo-ios-swift-resources.sh\"\n"; @@ -461,6 +480,7 @@ buildActionMask = 2147483647; files = ( 6352AA5F24DC7AE9002E66EB /* IdentifyViewController.swift in Sources */, + 536D9E922EA0DD37006D86BE /* PrebidBannerViewController.swift in Sources */, 6352AA5B24DC7AE9002E66EB /* AppDelegate.swift in Sources */, 631466ED24F7F555007DCA5D /* GAMBannerViewController.swift in Sources */, 6352AA5D24DC7AE9002E66EB /* SceneDelegate.swift in Sources */, diff --git a/demo-ios-swift/demo-ios-swift/AppDelegate.swift b/demo-ios-swift/demo-ios-swift/AppDelegate.swift index 907de8b..92fc3ee 100644 --- a/demo-ios-swift/demo-ios-swift/AppDelegate.swift +++ b/demo-ios-swift/demo-ios-swift/AppDelegate.swift @@ -9,6 +9,9 @@ import UIKit import OptableSDK +import PrebidMobile +import GoogleMobileAds + // The OPTABLE global points to an instance of OptableSDK which is initialized in the AppDelegate application() method at app launch. // While we could have initialized the global directly here, due to Swift lazy-loading this would delay initialization to the first // use of the SDK. While not strictly required, we want to force early initialization so that the SDK can detect the correct useragent @@ -19,14 +22,35 @@ var OPTABLE: OptableSDK? @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - // Override point for customization after application launch. + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { - // See comment further above on why we are initializing OptableSDK() from here: - OPTABLE = OptableSDK(host: "sandbox.optable.co", app: "ios-sdk-demo") + initOptable() + + initPrebidMobile() + initGoogleMobileAds() return true } + + private func initOptable() { + // See comment further above on why we are initializing OptableSDK() from here: + OPTABLE = OptableSDK(host: "sandbox.optable.co", app: "ios-sdk-demo") + } + + private func initPrebidMobile() { + Prebid.shared.prebidServerAccountId = "0689a263-318d-448b-a3d4-b02e8a709d9d" + + try? Prebid.initializeSDK( + serverURL: "https://prebid-server-test-j.prebid.org/openrtb2/auction" + ) + } + + private func initGoogleMobileAds() { + MobileAds.shared.start() + } // MARK: UISceneSession Lifecycle diff --git a/demo-ios-swift/demo-ios-swift/Base.lproj/Main.storyboard b/demo-ios-swift/demo-ios-swift/Base.lproj/Main.storyboard index 1bf46bf..a6363a9 100644 --- a/demo-ios-swift/demo-ios-swift/Base.lproj/Main.storyboard +++ b/demo-ios-swift/demo-ios-swift/Base.lproj/Main.storyboard @@ -204,6 +204,7 @@ + @@ -232,6 +233,7 @@ + @@ -244,10 +246,117 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -259,7 +368,7 @@ - + diff --git a/demo-ios-swift/demo-ios-swift/GAMBannerViewController.swift b/demo-ios-swift/demo-ios-swift/GAMBannerViewController.swift index 4a8fa0d..886bb35 100644 --- a/demo-ios-swift/demo-ios-swift/GAMBannerViewController.swift +++ b/demo-ios-swift/demo-ios-swift/GAMBannerViewController.swift @@ -9,12 +9,14 @@ import UIKit import GoogleMobileAds +fileprivate let AD_MANAGER_AD_UNIT_ID = "/22081946781/ios-sdk-demo/mobile-leaderboard" + class GAMBannerViewController: UIViewController { + // MARK: - GoogleMobileAds var bannerView: BannerView! - // MARK: Properties - + // MARK: - Outlets @IBOutlet weak var adPlaceholder: UIView! @IBOutlet weak var loadBannerButton: UIButton! @IBOutlet weak var loadBannerFromCacheButton: UIButton! @@ -27,35 +29,28 @@ class GAMBannerViewController: UIViewController { bannerView = BannerView(adSize: AdSizeBanner) addBannerViewToView(bannerView) bannerView.rootViewController = self + bannerView.adUnitID = AD_MANAGER_AD_UNIT_ID } - //MARK: Actions - @IBAction func loadBannerWithTargeting(_ sender: UIButton) { + setOutput("๐Ÿ“ก Calling /targeting API...\n\n") + do { - targetingOutput.text = "Calling /targeting API...\n\n" - - try OPTABLE!.targeting() { result in + try OPTABLE!.targeting() { [weak self] result in var tdata: NSDictionary = [:] switch result { case .success(let keyvalues): print("[OptableSDK] Success on /targeting API call: \(keyvalues)") - tdata = keyvalues - - DispatchQueue.main.async { - self.targetingOutput.text += "Data: \(keyvalues)\n" - } + self?.appendOutput("โœ… Data: \(keyvalues)\n") case .failure(let error): print("[OptableSDK] Error on /targeting API call: \(error)") - DispatchQueue.main.async { - self.targetingOutput.text += "๐Ÿšซ Error: \(error)\n" - } + self?.appendOutput("๐Ÿšซ Error: \(error)\n") } - self.loadBanner(adUnitID: "/22081946781/ios-sdk-demo/mobile-leaderboard", keyvalues: tdata) + self?.loadBanner(keyvalues: tdata) } } catch { print("[OptableSDK] Exception: \(error)") @@ -63,30 +58,28 @@ class GAMBannerViewController: UIViewController { } @IBAction func loadBannerWithTargetingFromCache(_ sender: UIButton) { - var tdata: NSDictionary = [:] - - targetingOutput.text = "Checking local targeting cache...\n\n" + setOutput("๐Ÿ—‚ Checking local targeting cache...\n\n") + var tdata: NSDictionary = [:] let cachedValues = OPTABLE!.targetingFromCache() - if (cachedValues != nil) { - print("[OptableSDK] Cached targeting values found: \(cachedValues!)") - targetingOutput.text += "\nFound cached data: \(cachedValues!)\n" - tdata = cachedValues! + + if let cachedValues { + print("[OptableSDK] Cached targeting values found: \(cachedValues)") + appendOutput("โœ… Found cached data: \(cachedValues)\n") + tdata = cachedValues } else { - targetingOutput.text += "\nCache empty.\n" + appendOutput("โ„น๏ธ Cache empty.\n") } - self.loadBanner(adUnitID: "/22081946781/ios-sdk-demo/mobile-leaderboard", keyvalues: tdata) + loadBanner(keyvalues: tdata) } @IBAction func clearTargetingCache(_ sender: UIButton) { - targetingOutput.text = "๐Ÿงน Clearing local targeting cache.\n" + setOutput("๐Ÿงน Clearing local targeting cache.\n") OPTABLE!.targetingClearCache() } - - private func loadBanner(adUnitID: String, keyvalues: NSDictionary) { - bannerView.adUnitID = adUnitID - + + private func loadBanner(keyvalues: NSDictionary) { let req = AdManagerRequest() req.customTargeting = keyvalues as? [String: String] bannerView.load(req) @@ -97,19 +90,15 @@ class GAMBannerViewController: UIViewController { private func witness() { do { - try OPTABLE!.witness(event: "GAMBannerViewController.loadBannerClicked", properties: ["example": "value"]) { result in + try OPTABLE!.witness( + event: "GAMBannerViewController.loadBannerClicked", + properties: ["example": "value"] + ) { [weak self] result in switch result { - case .success(let response): - print("[OptableSDK] Success on /witness API call: response.statusCode = \(response.statusCode)") - DispatchQueue.main.async { - self.targetingOutput.text += "\nโœ… Success calling witness API to log loadBannerClicked event.\n" - } - + case .success: + self?.appendOutput("\nโœ… Success calling witness API to log loadBannerClicked event.\n") case .failure(let error): - print("[OptableSDK] Error on /witness API call: \(error)") - DispatchQueue.main.async { - self.targetingOutput.text += "\n๐Ÿšซ Error: \(error)" - } + self?.appendOutput("\n๐Ÿšซ Error: \(error)\n") } } } catch { @@ -119,19 +108,14 @@ class GAMBannerViewController: UIViewController { private func profile() { do { - try OPTABLE!.profile(traits: ["example": "value", "anotherExample": 123, "thirdExample": true ]) { result in + try OPTABLE!.profile( + traits: ["example": "value", "anotherExample": 123, "thirdExample": true] + ) { [weak self] result in switch result { - case .success(let response): - print("[OptableSDK] Success on /profile API call: response.statusCode = \(response.statusCode)") - DispatchQueue.main.async { - self.targetingOutput.text += "\nโœ… Success calling profile API to set example traits.\n" - } - + case .success: + self?.appendOutput("\nโœ… Success calling profile API to set example traits.\n") case .failure(let error): - print("[OptableSDK] Error on /profile API call: \(error)") - DispatchQueue.main.async { - self.targetingOutput.text += "\n๐Ÿšซ Error: \(error)" - } + self?.appendOutput("\n๐Ÿšซ Error: \(error)\n") } } } catch { @@ -139,6 +123,20 @@ class GAMBannerViewController: UIViewController { } } + // MARK: - Helpers + + private func setOutput(_ text: String) { + DispatchQueue.main.async { + self.targetingOutput.text = text + } + } + + private func appendOutput(_ text: String) { + DispatchQueue.main.async { + self.targetingOutput.text += text + } + } + private func addBannerViewToView(_ bannerView: BannerView) { bannerView.translatesAutoresizingMaskIntoConstraints = false adPlaceholder.addSubview(bannerView) diff --git a/demo-ios-swift/demo-ios-swift/PrebidBannerViewController.swift b/demo-ios-swift/demo-ios-swift/PrebidBannerViewController.swift new file mode 100644 index 0000000..5d6b2d3 --- /dev/null +++ b/demo-ios-swift/demo-ios-swift/PrebidBannerViewController.swift @@ -0,0 +1,197 @@ +// +// PrebidBannerViewController.swift +// demo-ios-swift +// +// Copyright ยฉ 2020 Optable Technologies Inc. All rights reserved. +// See LICENSE for details. +// + +import UIKit +import PrebidMobile +import GoogleMobileAds + +fileprivate let AD_MANAGER_AD_UNIT_ID = "/21808260008/prebid_demo_app_original_api_banner" +fileprivate let PREBID_STORED_IMP = "prebid-demo-banner-320-50" + +class PrebidBannerViewController: UIViewController { + + // MARK: - PrebidMobile + private var pbmBannerAdUnit: BannerAdUnit! + + // MARK: - GoogleMobileAds + private var adManagerBannerView: AdManagerBannerView! + + // MARK: - Outlets + @IBOutlet weak var adPlaceholder: UIView! + @IBOutlet weak var loadBannerButton: UIButton! + @IBOutlet weak var loadBannerFromCacheButton: UIButton! + @IBOutlet weak var clearTargetingCacheButton: UIButton! + @IBOutlet weak var targetingOutput: UITextView! + + override func viewDidLoad() { + super.viewDidLoad() + + pbmBannerAdUnit = BannerAdUnit( + configId: PREBID_STORED_IMP, + size: .init(width: 320, height: 50) + ) + + adManagerBannerView = AdManagerBannerView(adSize: AdSizeBanner) + adManagerBannerView.adUnitID = AD_MANAGER_AD_UNIT_ID + adManagerBannerView.rootViewController = self + adManagerBannerView.delegate = self + addBannerViewToView(adManagerBannerView) + } + + @IBAction func loadBannerWithTargeting(_ sender: UIButton) { + setOutput("๐Ÿ“ก Calling /targeting API...\n\n") + + do { + try OPTABLE!.targeting { [weak self] result in + var tdata: NSDictionary = [:] + + switch result { + case .success(let keyvalues): + print("[OptableSDK] Success on /targeting API call: \(keyvalues)") + tdata = keyvalues + self?.appendOutput("โœ… Targeting data:\n\(keyvalues)\n") + + case .failure(let error): + print("[OptableSDK] Error on /targeting API call: \(error)") + self?.appendOutput("๐Ÿšซ Error: \(error.localizedDescription)\n") + } + + self?.loadBanner(keyvalues: tdata) + } + } catch { + print("[OptableSDK] Exception: \(error)") + appendOutput("โš ๏ธ Exception: \(error.localizedDescription)\n") + } + } + + @IBAction func loadBannerWithTargetingFromCache(_ sender: UIButton) { + setOutput("๐Ÿ—‚ Checking local targeting cache...\n\n") + + var tdata: NSDictionary = [:] + if let cachedValues = OPTABLE!.targetingFromCache() { + print("[OptableSDK] Cached targeting values found: \(cachedValues)") + appendOutput("โœ… Found cached data:\n\(cachedValues)\n") + tdata = cachedValues + } else { + appendOutput("โ„น๏ธ Cache empty.\n") + } + + loadBanner(keyvalues: tdata) + } + + @IBAction func clearTargetingCache(_ sender: UIButton) { + setOutput("๐Ÿงน Clearing local targeting cache.\n") + OPTABLE!.targetingClearCache() + } + + private func loadBanner(keyvalues: NSDictionary) { + let request = AdManagerRequest() + + pbmBannerAdUnit.fetchDemand(adObject: request) { [weak self] status in + if status != .prebidDemandFetchSuccess { + print("[PrebidMobile SDK] Prebid fetch demand was not successful: \(status.name())") + } + + // TODO: - Where should keyvalues go in Prebid and GAM(?) ? + if let keyvalues = keyvalues as? [String: String] { + request.customTargeting?.merge(keyvalues, uniquingKeysWith: { $1 }) + } + + self?.adManagerBannerView.load(request) + } + + witness() + profile() + } + + private func witness() { + do { + try OPTABLE!.witness( + event: "PrebidBannerViewController.loadBannerClicked", + properties: ["example": "value"] + ) { [weak self] result in + switch result { + case .success(let response): + print("[OptableSDK] Witness success: \(response.statusCode)") + self?.appendOutput("โœ… Witness API logged loadBannerClicked event.\n") + case .failure(let error): + print("[OptableSDK] Witness error: \(error)") + self?.appendOutput("๐Ÿšซ Witness error: \(error.localizedDescription)\n") + } + } + } catch { + print("[OptableSDK] Exception: \(error)") + appendOutput("โš ๏ธ Witness exception: \(error.localizedDescription)\n") + } + } + + private func profile() { + do { + try OPTABLE!.profile( + traits: ["example": "value", "anotherExample": 123, "thirdExample": true] + ) { [weak self] result in + switch result { + case .success(let response): + print("[OptableSDK] Profile success: \(response.statusCode)") + self?.appendOutput("โœ… Profile API set example traits.\n") + case .failure(let error): + print("[OptableSDK] Profile error: \(error)") + self?.appendOutput("๐Ÿšซ Profile error: \(error.localizedDescription)\n") + } + } + } catch { + print("[OptableSDK] Exception: \(error)") + appendOutput("โš ๏ธ Profile exception: \(error.localizedDescription)\n") + } + } + + // MARK: - Helpers + private func addBannerViewToView(_ bannerView: AdManagerBannerView) { + bannerView.translatesAutoresizingMaskIntoConstraints = false + adPlaceholder.addSubview(bannerView) + + NSLayoutConstraint.activate([ + bannerView.centerXAnchor.constraint(equalTo: adPlaceholder.centerXAnchor), + bannerView.centerYAnchor.constraint(equalTo: adPlaceholder.centerYAnchor) + ]) + } + + /// Safely sets the full text of targetingOutput on the main thread. + private func setOutput(_ text: String) { + DispatchQueue.main.async { + self.targetingOutput.text = text + } + } + + /// Appends text to targetingOutput on the main thread with line break. + private func appendOutput(_ text: String) { + DispatchQueue.main.async { + self.targetingOutput.text += "\n\(text)" + } + } +} + +// MARK: - GoogleMobileAds.BannerViewDelegate +extension PrebidBannerViewController: GoogleMobileAds.BannerViewDelegate { + + func bannerViewDidReceiveAd(_ bannerView: GoogleMobileAds.BannerView) { + AdViewUtils.findPrebidCreativeSize(bannerView, success: { size in + guard let bannerView = bannerView as? AdManagerBannerView else { return } + bannerView.resize(adSizeFor(cgSize: size)) + }, failure: { error in + print("[PrebidMobile SDK] Error finding creative size: \(error)") + }) + } + + func bannerView( + _ bannerView: GoogleMobileAds.BannerView, + didFailToReceiveAdWithError error: any Error + ) { + print("[GMA SDK] Failed to receive ad: \(error)") + } +}