// // MAUnityAdManager.m // AppLovin MAX Unity Plugin // #import "MAUnityAdManager.h" #define VERSION @"6.5.2" #define KEY_WINDOW [UIApplication sharedApplication].keyWindow #define DEVICE_SPECIFIC_ADVIEW_AD_FORMAT ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) ? MAAdFormat.leader : MAAdFormat.banner #define IS_VERTICAL_BANNER_POSITION(_POS) ( [@"center_left" isEqual: adViewPosition] || [@"center_right" isEqual: adViewPosition] ) #define DEGREES_TO_RADIANS(angle) ((angle) / 180.0 * M_PI) #ifdef __cplusplus extern "C" { #endif // UnityAppController.mm UIViewController* UnityGetGLViewController(void); UIWindow* UnityGetMainWindow(void); // life cycle management int UnityIsPaused(void); void UnityPause(int pause); void max_unity_dispatch_on_main_thread(dispatch_block_t block) { if ( block ) { if ( [NSThread isMainThread] ) { block(); } else { dispatch_async(dispatch_get_main_queue(), block); } } } #ifdef __cplusplus } #endif @interface MAUnityAdManager() // Parent Fields @property (nonatomic, weak) ALSdk *sdk; // Fullscreen Ad Fields @property (nonatomic, strong) NSMutableDictionary *interstitials; @property (nonatomic, strong) NSMutableDictionary *appOpenAds; @property (nonatomic, strong) NSMutableDictionary *rewardedAds; @property (nonatomic, strong) NSMutableDictionary *rewardedInterstitialAds; // AdView Fields @property (nonatomic, strong) NSMutableDictionary *adViews; @property (nonatomic, strong) NSMutableDictionary *adViewAdFormats; @property (nonatomic, strong) NSMutableDictionary *adViewPositions; @property (nonatomic, strong) NSMutableDictionary *adViewOffsets; @property (nonatomic, strong) NSMutableDictionary *adViewWidths; @property (nonatomic, strong) NSMutableDictionary *verticalAdViewFormats; @property (nonatomic, strong) NSMutableDictionary *> *adViewConstraints; @property (nonatomic, strong) NSMutableDictionary *> *adViewExtraParametersToSetAfterCreate; @property (nonatomic, strong) NSMutableDictionary *> *adViewLocalExtraParametersToSetAfterCreate; @property (nonatomic, strong) NSMutableDictionary *adViewCustomDataToSetAfterCreate; @property (nonatomic, strong) NSMutableArray *adUnitIdentifiersToShowAfterCreate; @property (nonatomic, strong) NSMutableSet *disabledAdaptiveBannerAdUnitIdentifiers; @property (nonatomic, strong) NSMutableSet *disabledAutoRefreshAdViewAdUnitIdentifiers; @property (nonatomic, strong) UIView *safeAreaBackground; @property (nonatomic, strong, nullable) UIColor *publisherBannerBackgroundColor; @property (nonatomic, strong) NSMutableDictionary *adInfoDict; @property (nonatomic, strong) NSObject *adInfoDictLock; @property (nonatomic, strong) NSOperationQueue *backgroundCallbackEventsQueue; @property (nonatomic, assign) BOOL resumeUnityAfterApplicationBecomesActive; @end // Internal @interface UIColor (ALUtils) + (nullable UIColor *)al_colorWithHexString:(NSString *)hexString; @end @interface NSNumber (ALUtils) + (NSNumber *)al_numberWithString:(NSString *)string; @end @interface NSString (ALUtils) @property (assign, readonly, getter=al_isValidString) BOOL al_validString; @end @interface MAAdFormat (ALUtils) @property (nonatomic, assign, readonly, getter=isFullscreenAd) BOOL fullscreenAd; @property (nonatomic, assign, readonly, getter=isAdViewAd) BOOL adViewAd; @end @implementation MAUnityAdManager static NSString *const SDK_TAG = @"AppLovinSdk"; static NSString *const TAG = @"MAUnityAdManager"; static NSString *const DEFAULT_AD_VIEW_POSITION = @"top_left"; static ALUnityBackgroundCallback backgroundCallback; #pragma mark - Initialization - (instancetype)init { self = [super init]; if ( self ) { self.interstitials = [NSMutableDictionary dictionaryWithCapacity: 2]; self.appOpenAds = [NSMutableDictionary dictionaryWithCapacity: 2]; self.rewardedAds = [NSMutableDictionary dictionaryWithCapacity: 2]; self.rewardedInterstitialAds = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViews = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViewAdFormats = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViewPositions = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViewOffsets = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViewWidths = [NSMutableDictionary dictionaryWithCapacity: 2]; self.verticalAdViewFormats = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViewConstraints = [NSMutableDictionary dictionaryWithCapacity: 2]; self.adViewExtraParametersToSetAfterCreate = [NSMutableDictionary dictionaryWithCapacity: 1]; self.adViewLocalExtraParametersToSetAfterCreate = [NSMutableDictionary dictionaryWithCapacity: 1]; self.adViewCustomDataToSetAfterCreate = [NSMutableDictionary dictionaryWithCapacity: 1]; self.adUnitIdentifiersToShowAfterCreate = [NSMutableArray arrayWithCapacity: 2]; self.disabledAdaptiveBannerAdUnitIdentifiers = [NSMutableSet setWithCapacity: 2]; self.disabledAutoRefreshAdViewAdUnitIdentifiers = [NSMutableSet setWithCapacity: 2]; self.adInfoDict = [NSMutableDictionary dictionary]; self.adInfoDictLock = [[NSObject alloc] init]; self.backgroundCallbackEventsQueue = [[NSOperationQueue alloc] init]; self.backgroundCallbackEventsQueue.maxConcurrentOperationCount = 1; max_unity_dispatch_on_main_thread(^{ self.safeAreaBackground = [[UIView alloc] init]; self.safeAreaBackground.hidden = YES; self.safeAreaBackground.backgroundColor = UIColor.clearColor; self.safeAreaBackground.translatesAutoresizingMaskIntoConstraints = NO; self.safeAreaBackground.userInteractionEnabled = NO; UIViewController *rootViewController = [self unityViewController]; [rootViewController.view addSubview: self.safeAreaBackground]; }); // Enable orientation change listener, so that the position can be updated for vertical banners. [[NSNotificationCenter defaultCenter] addObserverForName: UIDeviceOrientationDidChangeNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { for ( NSString *adUnitIdentifier in self.verticalAdViewFormats ) { [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: self.verticalAdViewFormats[adUnitIdentifier]]; } }]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationPaused:) name: UIApplicationDidEnterBackgroundNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(applicationResumed:) name: UIApplicationDidBecomeActiveNotification object: nil]; [[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidBecomeActiveNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) { #if !IS_TEST_APP if ( self.resumeUnityAfterApplicationBecomesActive && UnityIsPaused() ) { UnityPause(NO); } #endif self.backgroundCallbackEventsQueue.suspended = NO; }]; } return self; } + (MAUnityAdManager *)shared { static dispatch_once_t token; static MAUnityAdManager *shared; dispatch_once(&token, ^{ shared = [[MAUnityAdManager alloc] init]; }); return shared; } #pragma mark - Plugin Initialization - (ALSdk *)initializeSdkWithSettings:(ALSdkSettings *)settings backgroundCallback:(ALUnityBackgroundCallback)unityBackgroundCallback andCompletionHandler:(ALSdkInitializationCompletionHandler)completionHandler { backgroundCallback = unityBackgroundCallback; NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary]; NSString *sdkKey = infoDict[@"AppLovinSdkKey"]; if ( [sdkKey al_isValidString] ) { self.sdk = [ALSdk sharedWithKey: sdkKey settings: settings]; } else { self.sdk = [ALSdk sharedWithSettings: settings]; } [self.sdk setPluginVersion: [@"Max-Unity-" stringByAppendingString: VERSION]]; self.sdk.mediationProvider = @"max"; [self.sdk initializeSdkWithCompletionHandler:^(ALSdkConfiguration *configuration) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // Note: internal state should be updated first completionHandler( configuration ); NSString *consentFlowUserGeographyStr = @(configuration.consentFlowUserGeography).stringValue; NSString *consentDialogStateStr = @(configuration.consentDialogState).stringValue; NSString *appTrackingStatus = @(configuration.appTrackingTransparencyStatus).stringValue; // Deliberately name it `appTrackingStatus` to be a bit more generic (in case Android introduces a similar concept) [self forwardUnityEventWithArgs: @{@"name" : @"OnSdkInitializedEvent", @"consentFlowUserGeography" : consentFlowUserGeographyStr, @"consentDialogState" : consentDialogStateStr, @"countryCode" : configuration.countryCode, @"appTrackingStatus" : appTrackingStatus, @"isSuccessfullyInitialized" : @([self.sdk isInitialized]), @"isTestModeEnabled" : @([configuration isTestModeEnabled])}]; }); }]; return self.sdk; } #pragma mark - Banners - (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)bannerPosition { [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] atPosition: bannerPosition withOffset: CGPointZero]; } - (void)createBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset { [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] atPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset)]; } - (void)loadBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self loadAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)setBannerBackgroundColorForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier hexColorCode:(nullable NSString *)hexColorCode { [self setAdViewBackgroundColorForAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] hexColorCode: hexColorCode]; } - (void)setBannerPlacement:(nullable NSString *)placement forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self setAdViewPlacement: placement forAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)startBannerAutoRefreshForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self startAdViewAutoRefreshForAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)stopBannerAutoRefreshForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self stopAdViewAutoRefreshForAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)setBannerWidth:(CGFloat)width forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self setAdViewWidth: width forAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)updateBannerPosition:(nullable NSString *)bannerPosition forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self updateAdViewPosition: bannerPosition withOffset: CGPointZero forAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)updateBannerPosition:(CGFloat)xOffset y:(CGFloat)yOffset forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self updateAdViewPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset) forAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)setBannerExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value { [self setAdViewExtraParameterForAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] key: key value: value]; } - (void)setBannerLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value { if ( !key ) { [self log: @"Failed to set local extra parameter: No key specified"]; return; } [self setAdViewLocalExtraParameterForAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier] key: key value: value]; } - (void)setBannerCustomData:(nullable NSString *)customData forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self setAdViewCustomData: customData forAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)showBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self showAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)hideBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self hideAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (NSString *)bannerLayoutForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { return [self adViewLayoutForAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } - (void)destroyBannerWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self destroyAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: [self adViewAdFormatForAdUnitIdentifier: adUnitIdentifier]]; } + (CGFloat)adaptiveBannerHeightForWidth:(CGFloat)width { return [DEVICE_SPECIFIC_ADVIEW_AD_FORMAT adaptiveSizeForWidth: width].height; } #pragma mark - MRECs - (void)createMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier atPosition:(nullable NSString *)mrecPosition { [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec atPosition: mrecPosition withOffset: CGPointZero]; } - (void)createMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier x:(CGFloat)xOffset y:(CGFloat)yOffset { [self createAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec atPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset)]; } - (void)loadMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self loadAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)setMRecPlacement:(nullable NSString *)placement forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self setAdViewPlacement: placement forAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)startMRecAutoRefreshForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self startAdViewAutoRefreshForAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)stopMRecAutoRefreshForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self stopAdViewAutoRefreshForAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)updateMRecPosition:(nullable NSString *)mrecPosition forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self updateAdViewPosition: mrecPosition withOffset: CGPointZero forAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)updateMRecPosition:(CGFloat)xOffset y:(CGFloat)yOffset forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self updateAdViewPosition: DEFAULT_AD_VIEW_POSITION withOffset: CGPointMake(xOffset, yOffset) forAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)setMRecExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value { [self setAdViewExtraParameterForAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec key: key value: value]; } - (void)setMRecLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value { if ( !key ) { [self log: @"Failed to set local extra parameter: No key specified"]; return; } [self setAdViewLocalExtraParameterForAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec key: key value: value]; } - (void)setMRecCustomData:(nullable NSString *)customData forAdUnitIdentifier:(nullable NSString *)adUnitIdentifier; { [self setAdViewCustomData: customData forAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)showMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self showAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)destroyMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self destroyAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (void)hideMRecWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { [self hideAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } - (NSString *)mrecLayoutForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { return [self adViewLayoutForAdUnitIdentifier: adUnitIdentifier adFormat: MAAdFormat.mrec]; } #pragma mark - Interstitials - (void)loadInterstitialWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MAInterstitialAd *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier]; [interstitial loadAd]; } - (BOOL)isInterstitialReadyWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MAInterstitialAd *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier]; return [interstitial isReady]; } - (void)showInterstitialWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData { MAInterstitialAd *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier]; [interstitial showAdForPlacement: placement customData: customData]; } - (void)setInterstitialExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value { MAInterstitialAd *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier]; [interstitial setExtraParameterForKey: key value: value]; } - (void)setInterstitialLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value { if ( !key ) { [self log: @"Failed to set local extra parameter: No key specified"]; return; } MAInterstitialAd *interstitial = [self retrieveInterstitialForAdUnitIdentifier: adUnitIdentifier]; [interstitial setLocalExtraParameterForKey: key value: value]; } #pragma mark - App Open Ads - (void)loadAppOpenAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MAAppOpenAd *appOpenAd = [self retrieveAppOpenAdForAdUnitIdentifier: adUnitIdentifier]; [appOpenAd loadAd]; } - (BOOL)isAppOpenAdReadyWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MAAppOpenAd *appOpenAd = [self retrieveAppOpenAdForAdUnitIdentifier: adUnitIdentifier]; return [appOpenAd isReady]; } - (void)showAppOpenAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData { MAAppOpenAd *appOpenAd = [self retrieveAppOpenAdForAdUnitIdentifier: adUnitIdentifier]; [appOpenAd showAdForPlacement: placement customData: customData]; } - (void)setAppOpenAdExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value { MAAppOpenAd *appOpenAd = [self retrieveAppOpenAdForAdUnitIdentifier: adUnitIdentifier]; [appOpenAd setExtraParameterForKey: key value: value]; } - (void)setAppOpenAdLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value { if ( !key ) { [self log: @"Failed to set local extra parameter: No key specified"]; return; } MAAppOpenAd *appOpenAd = [self retrieveAppOpenAdForAdUnitIdentifier: adUnitIdentifier]; [appOpenAd setLocalExtraParameterForKey: key value: value]; } #pragma mark - Rewarded - (void)loadRewardedAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MARewardedAd *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedAd loadAd]; } - (BOOL)isRewardedAdReadyWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MARewardedAd *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier]; return [rewardedAd isReady]; } - (void)showRewardedAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData { MARewardedAd *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedAd showAdForPlacement: placement customData: customData]; } - (void)setRewardedAdExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value { MARewardedAd *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedAd setExtraParameterForKey: key value: value]; } - (void)setRewardedAdLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value; { if ( !key ) { [self log: @"Failed to set local extra parameter: No key specified"]; return; } MARewardedAd *rewardedAd = [self retrieveRewardedAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedAd setLocalExtraParameterForKey: key value: value]; } #pragma mark - Rewarded Interstitials - (void)loadRewardedInterstitialAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedInterstitialAd loadAd]; } - (BOOL)isRewardedInterstitialAdReadyWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier]; return [rewardedInterstitialAd isReady]; } - (void)showRewardedInterstitialAdWithAdUnitIdentifier:(nullable NSString *)adUnitIdentifier placement:(nullable NSString *)placement customData:(nullable NSString *)customData { MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedInterstitialAd showAdForPlacement: placement customData: customData]; } - (void)setRewardedInterstitialAdExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable NSString *)value { MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedInterstitialAd setExtraParameterForKey: key value: value]; } - (void)setRewardedInterstitialAdLocalExtraParameterForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier key:(nullable NSString *)key value:(nullable id)value { if ( !key ) { [self log: @"Failed to set local extra parameter: No key specified"]; return; } MARewardedInterstitialAd *rewardedInterstitialAd = [self retrieveRewardedInterstitialAdForAdUnitIdentifier: adUnitIdentifier]; [rewardedInterstitialAd setLocalExtraParameterForKey: key value: value]; } #pragma mark - Event Tracking - (void)trackEvent:(nullable NSString *)event parameters:(nullable NSString *)parameters { NSDictionary *deserializedParameters = [MAUnityAdManager deserializeParameters: parameters]; [self.sdk.eventService trackEvent: event parameters: deserializedParameters]; } #pragma mark - Ad Info - (NSString *)adInfoForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier { if ( ![adUnitIdentifier al_isValidString] ) return @""; MAAd *ad = [self adWithAdUnitIdentifier: adUnitIdentifier]; if ( !ad ) return @""; return [MAUnityAdManager serializeParameters: [self adInfoForAd: ad]]; } - (NSDictionary *)adInfoForAd:(MAAd *)ad { return @{@"adUnitId" : ad.adUnitIdentifier, @"adFormat" : ad.format.label, @"networkName" : ad.networkName, @"networkPlacement" : ad.networkPlacement, @"creativeId" : ad.creativeIdentifier ?: @"", @"placement" : ad.placement ?: @"", @"revenue" : [@(ad.revenue) stringValue], @"revenuePrecision" : ad.revenuePrecision, @"waterfallInfo" : [self createAdWaterfallInfo: ad.waterfall], @"latencyMillis" : [self requestLatencyMillisFromRequestLatency: ad.requestLatency], @"dspName" : ad.DSPName ?: @""}; } #pragma mark - Waterfall Information - (NSDictionary *)createAdWaterfallInfo:(MAAdWaterfallInfo *)waterfallInfo { NSMutableDictionary *waterfallInfoDict = [NSMutableDictionary dictionary]; if ( !waterfallInfo ) return waterfallInfoDict; waterfallInfoDict[@"name"] = waterfallInfo.name; waterfallInfoDict[@"testName"] = waterfallInfo.testName; NSMutableArray *> *networkResponsesArray = [NSMutableArray arrayWithCapacity: waterfallInfo.networkResponses.count]; for ( MANetworkResponseInfo *response in waterfallInfo.networkResponses ) { [networkResponsesArray addObject: [self createNetworkResponseInfo: response]]; } waterfallInfoDict[@"networkResponses"] = networkResponsesArray; waterfallInfoDict[@"latencyMillis"] = [self requestLatencyMillisFromRequestLatency: waterfallInfo.latency]; return waterfallInfoDict; } - (NSDictionary *)createNetworkResponseInfo:(MANetworkResponseInfo *)response { NSMutableDictionary *networkResponseDict = [NSMutableDictionary dictionary]; networkResponseDict[@"adLoadState"] = @(response.adLoadState).stringValue; MAMediatedNetworkInfo *mediatedNetworkInfo = response.mediatedNetwork; if ( mediatedNetworkInfo ) { NSMutableDictionary *networkInfoObject = [NSMutableDictionary dictionary]; networkInfoObject[@"name"] = response.mediatedNetwork.name; networkInfoObject[@"adapterClassName"] = response.mediatedNetwork.adapterClassName; networkInfoObject[@"adapterVersion"] = response.mediatedNetwork.adapterVersion; networkInfoObject[@"sdkVersion"] = response.mediatedNetwork.sdkVersion; networkResponseDict[@"mediatedNetwork"] = networkInfoObject; } networkResponseDict[@"credentials"] = response.credentials; networkResponseDict[@"isBidding"] = @([response isBidding]); MAError *error = response.error; if ( error ) { NSMutableDictionary *errorObject = [NSMutableDictionary dictionary]; errorObject[@"errorMessage"] = error.message; errorObject[@"adLoadFailure"] = error.adLoadFailureInfo; errorObject[@"errorCode"] = @(error.code).stringValue; errorObject[@"latencyMillis"] = [self requestLatencyMillisFromRequestLatency: error.requestLatency]; networkResponseDict[@"error"] = errorObject; } networkResponseDict[@"latencyMillis"] = [self requestLatencyMillisFromRequestLatency: response.latency]; return networkResponseDict; } #pragma mark - Ad Value - (NSString *)adValueForAdUnitIdentifier:(nullable NSString *)adUnitIdentifier withKey:(nullable NSString *)key { if ( adUnitIdentifier == nil || adUnitIdentifier.length == 0 ) return @""; if ( key == nil || key.length == 0 ) return @""; MAAd *ad = [self adWithAdUnitIdentifier: adUnitIdentifier]; if ( !ad ) return @""; return [ad adValueForKey: key]; } #pragma mark - Ad Callbacks - (void)didLoadAd:(MAAd *)ad { NSString *name; MAAdFormat *adFormat = ad.format; if ( [adFormat isAdViewAd] ) { MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: ad.adUnitIdentifier adFormat: adFormat]; // An ad is now being shown, enable user interaction. adView.userInteractionEnabled = YES; if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdLoadedEvent"; } else { name = @"OnBannerAdLoadedEvent"; } [self positionAdViewForAd: ad]; // Do not auto-refresh by default if the ad view is not showing yet (e.g. first load during app launch and publisher does not automatically show banner upon load success) // We will resume auto-refresh in -[MAUnityAdManager showBannerWithAdUnitIdentifier:]. if ( adView && [adView isHidden] ) { [adView stopAutoRefresh]; } } else if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialLoadedEvent"; } else if ( MAAdFormat.appOpen == adFormat ) { name = @"OnAppOpenAdLoadedEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdLoadedEvent"; } else if ( MAAdFormat.rewardedInterstitial == adFormat ) { name = @"OnRewardedInterstitialAdLoadedEvent"; } else { [self logInvalidAdFormat: adFormat]; return; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized ( self.adInfoDictLock ) { self.adInfoDict[ad.adUnitIdentifier] = ad; } NSDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; [self forwardUnityEventWithArgs: args]; }); } - (void)didFailToLoadAdForAdUnitIdentifier:(NSString *)adUnitIdentifier withError:(MAError *)error { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if ( !adUnitIdentifier ) { [self log: @"adUnitIdentifier cannot be nil from %@", [NSThread callStackSymbols]]; return; } NSString *name; if ( self.adViews[adUnitIdentifier] ) { MAAdFormat *adFormat = self.adViewAdFormats[adUnitIdentifier]; if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdLoadFailedEvent"; } else { name = @"OnBannerAdLoadFailedEvent"; } } else if ( self.interstitials[adUnitIdentifier] ) { name = @"OnInterstitialLoadFailedEvent"; } else if ( self.appOpenAds[adUnitIdentifier] ) { name = @"OnAppOpenAdLoadFailedEvent"; } else if ( self.rewardedAds[adUnitIdentifier] ) { name = @"OnRewardedAdLoadFailedEvent"; } else if ( self.rewardedInterstitialAds[adUnitIdentifier] ) { name = @"OnRewardedInterstitialAdLoadFailedEvent"; } else { [self log: @"invalid adUnitId from %@", [NSThread callStackSymbols]]; return; } @synchronized ( self.adInfoDictLock ) { [self.adInfoDict removeObjectForKey: adUnitIdentifier]; } [self forwardUnityEventWithArgs: @{@"name" : name, @"adUnitId" : adUnitIdentifier, @"errorCode" : [@(error.code) stringValue], @"errorMessage" : error.message, @"waterfallInfo" : [self createAdWaterfallInfo: error.waterfall], @"adLoadFailureInfo" : error.adLoadFailureInfo ?: @"", @"latencyMillis" : [self requestLatencyMillisFromRequestLatency: error.requestLatency]}]; }); } - (void)didClickAd:(MAAd *)ad { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; MAAdFormat *adFormat = ad.format; if ( MAAdFormat.banner == adFormat || MAAdFormat.leader == adFormat ) { name = @"OnBannerAdClickedEvent"; } else if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdClickedEvent"; } else if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialClickedEvent"; } else if ( MAAdFormat.appOpen == adFormat ) { name = @"OnAppOpenAdClickedEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdClickedEvent"; } else if ( MAAdFormat.rewardedInterstitial == adFormat ) { name = @"OnRewardedInterstitialAdClickedEvent"; } else { [self logInvalidAdFormat: adFormat]; return; } NSDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; [self forwardUnityEventWithArgs: args]; }); } - (void)didDisplayAd:(MAAd *)ad { // BMLs do not support [DISPLAY] events in Unity MAAdFormat *adFormat = ad.format; if ( ![adFormat isFullscreenAd] ) return; #if !IS_TEST_APP // UnityPause needs to be called on the main thread. UnityPause(YES); #endif dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialDisplayedEvent"; } else if ( MAAdFormat.appOpen == adFormat ) { name = @"OnAppOpenAdDisplayedEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdDisplayedEvent"; } else // rewarded inters { name = @"OnRewardedInterstitialAdDisplayedEvent"; } NSDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; [self forwardUnityEventWithArgs: args]; }); } - (void)didFailToDisplayAd:(MAAd *)ad withError:(MAError *)error { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // BMLs do not support [DISPLAY] events in Unity MAAdFormat *adFormat = ad.format; if ( ![adFormat isFullscreenAd] ) return; NSString *name; if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialAdFailedToDisplayEvent"; } else if ( MAAdFormat.appOpen == adFormat ) { name = @"OnAppOpenAdFailedToDisplayEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdFailedToDisplayEvent"; } else // rewarded inters { name = @"OnRewardedInterstitialAdFailedToDisplayEvent"; } NSMutableDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; args[@"errorCode"] = [@(error.code) stringValue]; args[@"errorMessage"] = error.message; args[@"mediatedNetworkErrorCode"] = [@(error.mediatedNetworkErrorCode) stringValue]; args[@"mediatedNetworkErrorMessage"] = error.mediatedNetworkErrorMessage; args[@"waterfallInfo"] = [self createAdWaterfallInfo: error.waterfall]; args[@"latencyMillis"] = [self requestLatencyMillisFromRequestLatency: error.requestLatency]; [self forwardUnityEventWithArgs: args]; }); } - (void)didHideAd:(MAAd *)ad { // BMLs do not support [HIDDEN] events in Unity MAAdFormat *adFormat = ad.format; if ( ![adFormat isFullscreenAd] ) return; #if !IS_TEST_APP extern bool _didResignActive; if ( _didResignActive ) { // If the application is not active, we should wait until application becomes active to resume unity. self.resumeUnityAfterApplicationBecomesActive = YES; } else { // UnityPause needs to be called on the main thread. UnityPause(NO); } #endif dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialHiddenEvent"; } else if ( MAAdFormat.appOpen == adFormat ) { name = @"OnAppOpenAdHiddenEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdHiddenEvent"; } else // rewarded inters { name = @"OnRewardedInterstitialAdHiddenEvent"; } NSDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; [self forwardUnityEventWithArgs: args]; }); } - (void)didExpandAd:(MAAd *)ad { MAAdFormat *adFormat = ad.format; if ( ![adFormat isAdViewAd] ) { [self logInvalidAdFormat: adFormat]; return; } #if !IS_TEST_APP // UnityPause needs to be called on the main thread. UnityPause(YES); #endif dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdExpandedEvent"; } else { name = @"OnBannerAdExpandedEvent"; } NSDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; [self forwardUnityEventWithArgs: args]; }); } - (void)didCollapseAd:(MAAd *)ad { MAAdFormat *adFormat = ad.format; if ( ![adFormat isAdViewAd] ) { [self logInvalidAdFormat: adFormat]; return; } #if !IS_TEST_APP extern bool _didResignActive; if ( _didResignActive ) { // If the application is not active, we should wait until application becomes active to resume unity. self.resumeUnityAfterApplicationBecomesActive = YES; } else { // UnityPause needs to be called on the main thread. UnityPause(NO); } #endif dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdCollapsedEvent"; } else { name = @"OnBannerAdCollapsedEvent"; } NSDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; [self forwardUnityEventWithArgs: args]; }); } - (void)didStartRewardedVideoForAd:(MAAd *)ad { // This event is not forwarded } - (void)didCompleteRewardedVideoForAd:(MAAd *)ad { // This event is not forwarded } - (void)didRewardUserForAd:(MAAd *)ad withReward:(MAReward *)reward { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ MAAdFormat *adFormat = ad.format; if ( adFormat != MAAdFormat.rewarded && adFormat != MAAdFormat.rewardedInterstitial ) { [self logInvalidAdFormat: adFormat]; return; } NSString *rewardLabel = reward ? reward.label : @""; NSInteger rewardAmountInt = reward ? reward.amount : 0; NSString *rewardAmount = [@(rewardAmountInt) stringValue]; NSString *name = (adFormat == MAAdFormat.rewarded) ? @"OnRewardedAdReceivedRewardEvent" : @"OnRewardedInterstitialAdReceivedRewardEvent"; NSMutableDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; args[@"rewardLabel"] = rewardLabel; args[@"rewardAmount"] = rewardAmount; [self forwardUnityEventWithArgs: args]; }); } - (void)didPayRevenueForAd:(MAAd *)ad { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; MAAdFormat *adFormat = ad.format; if ( MAAdFormat.banner == adFormat || MAAdFormat.leader == adFormat ) { name = @"OnBannerAdRevenuePaidEvent"; } else if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdRevenuePaidEvent"; } else if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialAdRevenuePaidEvent"; } else if ( MAAdFormat.appOpen == adFormat ) { name = @"OnAppOpenAdRevenuePaidEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdRevenuePaidEvent"; } else if ( MAAdFormat.rewardedInterstitial == adFormat ) { name = @"OnRewardedInterstitialAdRevenuePaidEvent"; } else { [self logInvalidAdFormat: adFormat]; return; } NSMutableDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; args[@"keepInBackground"] = @([adFormat isFullscreenAd]); [self forwardUnityEventWithArgs: args]; }); } - (void)didGenerateCreativeIdentifier:(NSString *)creativeIdentifier forAd:(MAAd *)ad { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *name; MAAdFormat *adFormat = ad.format; if ( MAAdFormat.banner == adFormat || MAAdFormat.leader == adFormat ) { name = @"OnBannerAdReviewCreativeIdGeneratedEvent"; } else if ( MAAdFormat.mrec == adFormat ) { name = @"OnMRecAdReviewCreativeIdGeneratedEvent"; } else if ( MAAdFormat.interstitial == adFormat ) { name = @"OnInterstitialAdReviewCreativeIdGeneratedEvent"; } else if ( MAAdFormat.rewarded == adFormat ) { name = @"OnRewardedAdReviewCreativeIdGeneratedEvent"; } else if ( MAAdFormat.rewardedInterstitial == adFormat ) { name = @"OnRewardedInterstitialAdReviewCreativeIdGeneratedEvent"; } else { [self logInvalidAdFormat: adFormat]; return; } NSMutableDictionary *args = [self defaultAdEventParametersForName: name withAd: ad]; args[@"adReviewCreativeId"] = creativeIdentifier; args[@"keepInBackground"] = @([adFormat isFullscreenAd]); // Forward the event in background for fullscreen ads so that the user gets the callback even while the ad is playing. [self forwardUnityEventWithArgs: args]; }); } - (NSMutableDictionary *)defaultAdEventParametersForName:(NSString *)name withAd:(MAAd *)ad { NSMutableDictionary *args = [[self adInfoForAd: ad] mutableCopy]; args[@"name"] = name; return args; } #pragma mark - Internal Methods - (void)createAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset { max_unity_dispatch_on_main_thread(^{ [self log: @"Creating %@ with ad unit identifier \"%@\" and position: \"%@\"", adFormat, adUnitIdentifier, adViewPosition]; if ( self.adViews[adUnitIdentifier] ) { [self log: @"Trying to create a %@ that was already created. This will cause the current ad to be hidden.", adFormat.label]; } // Retrieve ad view from the map MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: adViewPosition withOffset: offset]; adView.hidden = YES; self.safeAreaBackground.hidden = YES; // Position ad view immediately so if publisher sets color before ad loads, it will not be the size of the screen self.adViewAdFormats[adUnitIdentifier] = adFormat; [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; NSDictionary *extraParameters = self.adViewExtraParametersToSetAfterCreate[adUnitIdentifier]; // Enable adaptive banners by default for banners and leaders. if ( [adFormat isBannerOrLeaderAd] ) { // Check if there is already a pending setting for adaptive banners. If not, enable them. if ( !extraParameters[@"adaptive_banner"] ) { [adView setExtraParameterForKey: @"adaptive_banner" value: @"true"]; } } // Handle initial extra parameters if publisher sets it before creating ad view if ( extraParameters ) { for ( NSString *key in extraParameters ) { [adView setExtraParameterForKey: key value: extraParameters[key]]; [self handleExtraParameterChangesIfNeededForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat key: key value: extraParameters[key]]; } [self.adViewExtraParametersToSetAfterCreate removeObjectForKey: adUnitIdentifier]; } // Handle initial local extra parameters if publisher sets it before creating ad view if ( self.adViewLocalExtraParametersToSetAfterCreate[adUnitIdentifier] ) { NSDictionary *localExtraParameters = self.adViewLocalExtraParametersToSetAfterCreate[adUnitIdentifier]; for ( NSString *key in localExtraParameters ) { [adView setLocalExtraParameterForKey: key value: localExtraParameters[key]]; } [self.adViewLocalExtraParametersToSetAfterCreate removeObjectForKey: adUnitIdentifier]; } // Handle initial custom data if publisher sets it before creating ad view if ( self.adViewCustomDataToSetAfterCreate[adUnitIdentifier] ) { NSString *customData = self.adViewCustomDataToSetAfterCreate[adUnitIdentifier]; adView.customData = customData; [self.adViewCustomDataToSetAfterCreate removeObjectForKey: adUnitIdentifier]; } [adView loadAd]; // Disable auto-refresh if publisher sets it before creating the ad view. if ( [self.disabledAutoRefreshAdViewAdUnitIdentifiers containsObject: adUnitIdentifier] ) { [adView stopAutoRefresh]; } // The publisher may have requested to show the banner before it was created. Now that the banner is created, show it. if ( [self.adUnitIdentifiersToShowAfterCreate containsObject: adUnitIdentifier] ) { [self showAdViewWithAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; [self.adUnitIdentifiersToShowAfterCreate removeObject: adUnitIdentifier]; } }); } - (void)loadAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( !adView ) { [self log: @"%@ does not exist for ad unit identifier \"%@\".", adFormat.label, adUnitIdentifier]; return; } if ( ![self.disabledAutoRefreshAdViewAdUnitIdentifiers containsObject: adUnitIdentifier] ) { if ( [adView isHidden] ) { [self log: @"Auto-refresh will resume when the %@ ad is shown. You should only call LoadBanner() or LoadMRec() if you explicitly pause auto-refresh and want to manually load an ad.", adFormat.label]; return; } [self log: @"You must stop auto-refresh if you want to manually load %@ ads.", adFormat.label]; return; } [adView loadAd]; }); } - (void)setAdViewBackgroundColorForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat hexColorCode:(NSString *)hexColorCode { max_unity_dispatch_on_main_thread(^{ [self log: @"Setting %@ with ad unit identifier \"%@\" to color: \"%@\"", adFormat, adUnitIdentifier, hexColorCode]; // In some cases, black color may get redrawn on each frame update, resulting in an undesired flicker NSString *hexColorCodeToUse = [hexColorCode containsString: @"FF000000"] ? @"FF000001" : hexColorCode; UIColor *convertedColor = [UIColor al_colorWithHexString: hexColorCodeToUse]; MAAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; self.publisherBannerBackgroundColor = convertedColor; self.safeAreaBackground.backgroundColor = view.backgroundColor = convertedColor; // Position adView to ensure logic that depends on background color is properly run [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; }); } - (void)setAdViewPlacement:(nullable NSString *)placement forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Setting placement \"%@\" for \"%@\" with ad unit identifier \"%@\"", placement, adFormat, adUnitIdentifier]; MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; adView.placement = placement; }); } - (void)startAdViewAutoRefreshForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Starting %@ auto refresh for ad unit identifier \"%@\"", adFormat.label, adUnitIdentifier]; [self.disabledAutoRefreshAdViewAdUnitIdentifiers removeObject: adUnitIdentifier]; MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( !adView ) { [self log: @"%@ does not exist for ad unit identifier %@.", adFormat.label, adUnitIdentifier]; return; } [adView startAutoRefresh]; }); } - (void)stopAdViewAutoRefreshForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Stopping %@ auto refresh for ad unit identifier \"%@\"", adFormat.label, adUnitIdentifier]; [self.disabledAutoRefreshAdViewAdUnitIdentifiers addObject: adUnitIdentifier]; MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( !adView ) { [self log: @"%@ does not exist for ad unit identifier %@.", adFormat.label, adUnitIdentifier]; return; } [adView stopAutoRefresh]; }); } - (void)setAdViewWidth:(CGFloat)width forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Setting width %f for \"%@\" with ad unit identifier \"%@\"", width, adFormat, adUnitIdentifier]; BOOL isBannerOrLeader = adFormat.isBannerOrLeaderAd; // Banners and leaders need to be at least 320pts wide. CGFloat minWidth = isBannerOrLeader ? MAAdFormat.banner.size.width : adFormat.size.width; if ( width < minWidth ) { [self log: @"The provided width: %f is smaller than the minimum required width: %f for ad format: %@. Automatically setting width to %f.", width, minWidth, adFormat, minWidth]; } CGFloat widthToSet = MAX( minWidth, width ); self.adViewWidths[adUnitIdentifier] = @(widthToSet); [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; }); } - (void)updateAdViewPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ self.adViewPositions[adUnitIdentifier] = adViewPosition; self.adViewOffsets[adUnitIdentifier] = [NSValue valueWithCGPoint: offset]; [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; }); } - (void)setAdViewExtraParameterForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat key:(NSString *)key value:(nullable NSString *)value { max_unity_dispatch_on_main_thread(^{ [self log: @"Setting %@ extra with key: \"%@\" value: \"%@\"", adFormat, key, value]; MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( adView ) { [adView setExtraParameterForKey: key value: value]; } else { [self log: @"%@ does not exist for ad unit identifier \"%@\". Saving extra parameter to be set when it is created.", adFormat, adUnitIdentifier]; // The adView has not yet been created. Store the extra parameters, so that they can be added once the banner has been created. NSMutableDictionary *extraParameters = self.adViewExtraParametersToSetAfterCreate[adUnitIdentifier]; if ( !extraParameters ) { extraParameters = [NSMutableDictionary dictionaryWithCapacity: 1]; self.adViewExtraParametersToSetAfterCreate[adUnitIdentifier] = extraParameters; } extraParameters[key] = value; } // Certain extra parameters need to be handled immediately [self handleExtraParameterChangesIfNeededForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat key: key value: value]; }); } - (void)setAdViewLocalExtraParameterForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat key:(NSString *)key value:(nullable id)value { max_unity_dispatch_on_main_thread(^{ [self log: @"Setting %@ local extra with key: \"%@\" value: \"%@\"", adFormat, key, value]; MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( adView ) { [adView setLocalExtraParameterForKey: key value: value]; } else { [self log: @"%@ does not exist for ad unit identifier \"%@\". Saving local extra parameter to be set when it is created.", adFormat, adUnitIdentifier]; // The adView has not yet been created. Store the loca extra parameters, so that they can be added once the adview has been created. NSMutableDictionary *localExtraParameters = self.adViewLocalExtraParametersToSetAfterCreate[adUnitIdentifier]; if ( !localExtraParameters ) { localExtraParameters = [NSMutableDictionary dictionaryWithCapacity: 1]; self.adViewLocalExtraParametersToSetAfterCreate[adUnitIdentifier] = localExtraParameters; } localExtraParameters[key] = value; } }); } - (void)setAdViewCustomData:(nullable NSString *)customData forAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( adView ) { adView.customData = customData; } else { [self log: @"%@ does not exist for ad unit identifier %@. Saving custom data to be set when it is created.", adFormat, adUnitIdentifier]; // The adView has not yet been created. Store the custom data, so that they can be added once the AdView has been created. self.adViewCustomDataToSetAfterCreate[adUnitIdentifier] = customData; } }); } - (void)handleExtraParameterChangesIfNeededForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat key:(NSString *)key value:(nullable NSString *)value { if ( MAAdFormat.mrec != adFormat ) { if ( [@"force_banner" isEqualToString: key] ) { BOOL shouldForceBanner = [NSNumber al_numberWithString: value].boolValue; MAAdFormat *forcedAdFormat = shouldForceBanner ? MAAdFormat.banner : DEVICE_SPECIFIC_ADVIEW_AD_FORMAT; self.adViewAdFormats[adUnitIdentifier] = forcedAdFormat; [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: forcedAdFormat]; } else if ( [@"adaptive_banner" isEqualToString: key] ) { BOOL shouldUseAdaptiveBanner = [NSNumber al_numberWithString: value].boolValue; if ( shouldUseAdaptiveBanner ) { [self.disabledAdaptiveBannerAdUnitIdentifiers removeObject: adUnitIdentifier]; } else { [self.disabledAdaptiveBannerAdUnitIdentifiers addObject: adUnitIdentifier]; } [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; } } } - (void)showAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Showing %@ with ad unit identifier \"%@\"", adFormat, adUnitIdentifier]; MAAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( !view ) { [self log: @"%@ does not exist for ad unit identifier %@.", adFormat, adUnitIdentifier]; // The adView has not yet been created. Store the ad unit ID, so that it can be displayed once the banner has been created. [self.adUnitIdentifiersToShowAfterCreate addObject: adUnitIdentifier]; } else { // Check edge case where ad may be detatched from view controller if ( !view.window.rootViewController ) { [self log: @"%@ missing view controller - re-attaching to %@...", adFormat, [self unityViewController]]; UIViewController *rootViewController = [self unityViewController]; [rootViewController.view addSubview: view]; [self positionAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; } } self.safeAreaBackground.hidden = NO; view.hidden = NO; if ( ![self.disabledAutoRefreshAdViewAdUnitIdentifiers containsObject: adUnitIdentifier] ) { [view startAutoRefresh]; } }); } - (void)hideAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Hiding %@ with ad unit identifier \"%@\"", adFormat, adUnitIdentifier]; [self.adUnitIdentifiersToShowAfterCreate removeObject: adUnitIdentifier]; MAAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; view.hidden = YES; self.safeAreaBackground.hidden = YES; [view stopAutoRefresh]; }); } - (NSString *)adViewLayoutForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { [self log: @"Getting %@ position with ad unit identifier \"%@\"", adFormat, adUnitIdentifier]; MAAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; if ( !view ) { [self log: @"%@ does not exist for ad unit identifier %@", adFormat, adUnitIdentifier]; return @""; } CGRect adViewFrame = view.frame; return [MAUnityAdManager serializeParameters: @{@"origin_x" : [NSString stringWithFormat: @"%f", adViewFrame.origin.x], @"origin_y" : [NSString stringWithFormat: @"%f", adViewFrame.origin.y], @"width" : [NSString stringWithFormat: @"%f", CGRectGetWidth(adViewFrame)], @"height" : [NSString stringWithFormat: @"%f", CGRectGetHeight(adViewFrame)]}]; } - (void)destroyAdViewWithAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ [self log: @"Destroying %@ with ad unit identifier \"%@\"", adFormat, adUnitIdentifier]; MAAdView *view = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; view.delegate = nil; view.revenueDelegate = nil; view.adReviewDelegate = nil; [view removeFromSuperview]; [self.adViews removeObjectForKey: adUnitIdentifier]; [self.adViewAdFormats removeObjectForKey: adUnitIdentifier]; [self.adViewPositions removeObjectForKey: adUnitIdentifier]; [self.adViewOffsets removeObjectForKey: adUnitIdentifier]; [self.adViewWidths removeObjectForKey: adUnitIdentifier]; [self.verticalAdViewFormats removeObjectForKey: adUnitIdentifier]; [self.disabledAdaptiveBannerAdUnitIdentifiers removeObject: adUnitIdentifier]; }); } - (void)logInvalidAdFormat:(MAAdFormat *)adFormat { [self log: @"invalid ad format: %@, from %@", adFormat, [NSThread callStackSymbols]]; } - (void)log:(NSString *)format, ... { va_list valist; va_start(valist, format); NSString *message = [[NSString alloc] initWithFormat: format arguments: valist]; va_end(valist); NSLog(@"[%@] [%@] %@", SDK_TAG, TAG, message); } + (void)log:(NSString *)format, ... { va_list valist; va_start(valist, format); NSString *message = [[NSString alloc] initWithFormat: format arguments: valist]; va_end(valist); NSLog(@"[%@] [%@] %@", SDK_TAG, TAG, message); } - (MAInterstitialAd *)retrieveInterstitialForAdUnitIdentifier:(NSString *)adUnitIdentifier { MAInterstitialAd *result = self.interstitials[adUnitIdentifier]; if ( !result ) { result = [[MAInterstitialAd alloc] initWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk]; result.delegate = self; result.revenueDelegate = self; result.adReviewDelegate = self; self.interstitials[adUnitIdentifier] = result; } return result; } - (MAAppOpenAd *)retrieveAppOpenAdForAdUnitIdentifier:(NSString *)adUnitIdentifier { MAAppOpenAd *result = self.appOpenAds[adUnitIdentifier]; if ( !result ) { result = [[MAAppOpenAd alloc] initWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk]; result.delegate = self; result.revenueDelegate = self; self.appOpenAds[adUnitIdentifier] = result; } return result; } - (MARewardedAd *)retrieveRewardedAdForAdUnitIdentifier:(NSString *)adUnitIdentifier { MARewardedAd *result = self.rewardedAds[adUnitIdentifier]; if ( !result ) { result = [MARewardedAd sharedWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk]; result.delegate = self; result.revenueDelegate = self; result.adReviewDelegate = self; self.rewardedAds[adUnitIdentifier] = result; } return result; } - (MARewardedInterstitialAd *)retrieveRewardedInterstitialAdForAdUnitIdentifier:(NSString *)adUnitIdentifier { MARewardedInterstitialAd *result = self.rewardedInterstitialAds[adUnitIdentifier]; if ( !result ) { result = [[MARewardedInterstitialAd alloc] initWithAdUnitIdentifier: adUnitIdentifier sdk: self.sdk]; result.delegate = self; result.revenueDelegate = self; result.adReviewDelegate = self; self.rewardedInterstitialAds[adUnitIdentifier] = result; } return result; } - (MAAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { return [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat atPosition: nil withOffset: CGPointZero]; } - (MAAdView *)retrieveAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat atPosition:(NSString *)adViewPosition withOffset:(CGPoint)offset { MAAdView *result = self.adViews[adUnitIdentifier]; if ( !result && adViewPosition ) { result = [[MAAdView alloc] initWithAdUnitIdentifier: adUnitIdentifier adFormat: adFormat sdk: self.sdk]; // There is a Unity bug where if an empty UIView is on screen with user interaction enabled, and a user interacts with it, it just passes the events to random parts of the screen. result.userInteractionEnabled = NO; result.translatesAutoresizingMaskIntoConstraints = NO; result.delegate = self; result.revenueDelegate = self; result.adReviewDelegate = self; self.adViews[adUnitIdentifier] = result; self.adViewPositions[adUnitIdentifier] = adViewPosition; self.adViewOffsets[adUnitIdentifier] = [NSValue valueWithCGPoint: offset]; UIViewController *rootViewController = [self unityViewController]; [rootViewController.view addSubview: result]; // Allow pubs to pause auto-refresh immediately, by default. [result setExtraParameterForKey: @"allow_pause_auto_refresh_immediately" value: @"true"]; } return result; } - (void)positionAdViewForAd:(MAAd *)ad { [self positionAdViewForAdUnitIdentifier: ad.adUnitIdentifier adFormat: ad.format]; } - (void)positionAdViewForAdUnitIdentifier:(NSString *)adUnitIdentifier adFormat:(MAAdFormat *)adFormat { max_unity_dispatch_on_main_thread(^{ MAAdView *adView = [self retrieveAdViewForAdUnitIdentifier: adUnitIdentifier adFormat: adFormat]; NSString *adViewPosition = self.adViewPositions[adUnitIdentifier]; NSValue *adViewPositionValue = self.adViewOffsets[adUnitIdentifier]; CGPoint adViewOffset = [adViewPositionValue CGPointValue]; BOOL isAdaptiveBannerDisabled = [self.disabledAdaptiveBannerAdUnitIdentifiers containsObject: adUnitIdentifier]; BOOL isWidthPtsOverridden = self.adViewWidths[adUnitIdentifier] != nil; UIView *superview = adView.superview; if ( !superview ) return; // Deactivate any previous constraints and reset rotation so that the banner can be positioned again. NSArray *activeConstraints = self.adViewConstraints[adUnitIdentifier]; [NSLayoutConstraint deactivateConstraints: activeConstraints]; adView.transform = CGAffineTransformIdentity; [self.verticalAdViewFormats removeObjectForKey: adUnitIdentifier]; // Ensure superview contains the safe area background. if ( ![superview.subviews containsObject: self.safeAreaBackground] ) { [self.safeAreaBackground removeFromSuperview]; [superview insertSubview: self.safeAreaBackground belowSubview: adView]; } // Deactivate any previous constraints and reset visibility state so that the safe area background can be positioned again. [NSLayoutConstraint deactivateConstraints: self.safeAreaBackground.constraints]; self.safeAreaBackground.hidden = adView.hidden; // // Determine ad width // CGFloat adViewWidth; // Check if publisher has overridden width as points if ( isWidthPtsOverridden ) { adViewWidth = self.adViewWidths[adUnitIdentifier].floatValue; } // Top center / bottom center stretches full screen else if ( [adViewPosition isEqual: @"top_center"] || [adViewPosition isEqual: @"bottom_center"] ) { adViewWidth = CGRectGetWidth(KEY_WINDOW.bounds); } // Else use standard widths of 320, 728, or 300 else { adViewWidth = adFormat.size.width; } // // Determine ad height // CGFloat adViewHeight; if ( (adFormat == MAAdFormat.banner || adFormat == MAAdFormat.leader) && !isAdaptiveBannerDisabled ) { adViewHeight = [adFormat adaptiveSizeForWidth: adViewWidth].height; } else { adViewHeight = adFormat.size.height; } CGSize adViewSize = CGSizeMake(adViewWidth, adViewHeight); // All positions have constant height NSMutableArray *constraints = [NSMutableArray arrayWithObject: [adView.heightAnchor constraintEqualToConstant: adViewSize.height]]; UILayoutGuide *layoutGuide = superview.safeAreaLayoutGuide; if ( [adViewPosition isEqual: @"top_center"] || [adViewPosition isEqual: @"bottom_center"] ) { // Non AdMob banners will still be of 50/90 points tall. Set the auto sizing mask such that the inner ad view is pinned to the bottom or top according to the ad view position. if ( !isAdaptiveBannerDisabled ) { adView.autoresizingMask = UIViewAutoresizingFlexibleWidth; if ( [@"top_center" isEqual: adViewPosition] ) { adView.autoresizingMask |= UIViewAutoresizingFlexibleBottomMargin; } else // bottom_center { adView.autoresizingMask |= UIViewAutoresizingFlexibleTopMargin; } } // If publisher actually provided a banner background color if ( self.publisherBannerBackgroundColor && adFormat != MAAdFormat.mrec ) { if ( isWidthPtsOverridden ) { [constraints addObjectsFromArray: @[[adView.widthAnchor constraintEqualToConstant: adViewWidth], [adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor], [self.safeAreaBackground.widthAnchor constraintEqualToConstant: adViewWidth], [self.safeAreaBackground.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor]]]; if ( [adViewPosition isEqual: @"top_center"] ) { [constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor], [self.safeAreaBackground.topAnchor constraintEqualToAnchor: superview.topAnchor], [self.safeAreaBackground.bottomAnchor constraintEqualToAnchor: adView.topAnchor]]]; } else // bottom_center { [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor], [self.safeAreaBackground.topAnchor constraintEqualToAnchor: adView.bottomAnchor], [self.safeAreaBackground.bottomAnchor constraintEqualToAnchor: superview.bottomAnchor]]]; } } else { [constraints addObjectsFromArray: @[[adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor], [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor], [self.safeAreaBackground.leftAnchor constraintEqualToAnchor: superview.leftAnchor], [self.safeAreaBackground.rightAnchor constraintEqualToAnchor: superview.rightAnchor]]]; if ( [adViewPosition isEqual: @"top_center"] ) { [constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor], [self.safeAreaBackground.topAnchor constraintEqualToAnchor: superview.topAnchor], [self.safeAreaBackground.bottomAnchor constraintEqualToAnchor: adView.topAnchor]]]; } else // bottom_center { [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor], [self.safeAreaBackground.topAnchor constraintEqualToAnchor: adView.bottomAnchor], [self.safeAreaBackground.bottomAnchor constraintEqualToAnchor: superview.bottomAnchor]]]; } } } // If pub does not have a background color set or this is not a banner else { self.safeAreaBackground.hidden = YES; [constraints addObjectsFromArray: @[[adView.widthAnchor constraintEqualToConstant: adViewWidth], [adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor]]]; if ( [adViewPosition isEqual: @"top_center"] ) { [constraints addObject: [adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor]]; } else // BottomCenter { [constraints addObject: [adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor]]; } } } // Check if the publisher wants vertical banners. else if ( [adViewPosition isEqual: @"center_left"] || [adViewPosition isEqual: @"center_right"] ) { if ( MAAdFormat.mrec == adFormat ) { [constraints addObject: [adView.widthAnchor constraintEqualToConstant: adViewSize.width]]; if ( [adViewPosition isEqual: @"center_left"] ) { [constraints addObjectsFromArray: @[[adView.centerYAnchor constraintEqualToAnchor: layoutGuide.centerYAnchor], [adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor]]]; [constraints addObjectsFromArray: @[[self.safeAreaBackground.rightAnchor constraintEqualToAnchor: layoutGuide.leftAnchor], [self.safeAreaBackground.leftAnchor constraintEqualToAnchor: superview.leftAnchor]]]; } else // center_right { [constraints addObjectsFromArray: @[[adView.centerYAnchor constraintEqualToAnchor: layoutGuide.centerYAnchor], [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor]]]; [constraints addObjectsFromArray: @[[self.safeAreaBackground.leftAnchor constraintEqualToAnchor: layoutGuide.rightAnchor], [self.safeAreaBackground.rightAnchor constraintEqualToAnchor: superview.rightAnchor]]]; } } else { /* Align the center of the view such that when rotated it snaps into place. * * +---+---+-------+ * | | | * | | | * | | | * | | | * | | | * | | | * +-------------+---+-----------+--+ * | | + | + | | * +-------------+---+-----------+--+ * <+> | | * |+ | | * || | | * || | | * || | | * || | | * +|--+-----------+ * v * Banner Half Height */ self.safeAreaBackground.hidden = YES; adView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, M_PI_2); CGFloat width; // If the publiser has a background color set - set the width to the height of the screen, to span the ad across the screen after it is rotated. if ( self.publisherBannerBackgroundColor ) { if ( isWidthPtsOverridden ) { width = adViewWidth; } else { width = CGRectGetHeight(KEY_WINDOW.bounds); } } // Otherwise - we shouldn't span the banner the width of the realm (there might be user-interactable UI on the sides) else { width = adViewWidth; } [constraints addObject: [adView.widthAnchor constraintEqualToConstant: width]]; // Set constraints such that the center of the banner aligns with the center left or right as needed. That way, once rotated, the banner snaps into place. [constraints addObject: [adView.centerYAnchor constraintEqualToAnchor: superview.centerYAnchor]]; // Place the center of the banner half the height of the banner away from the side. If we align the center exactly with the left/right anchor, only half the banner will be visible. CGFloat bannerHalfHeight = adViewSize.height / 2.0; UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation; if ( [adViewPosition isEqual: @"center_left"] ) { NSLayoutAnchor *anchor = ( orientation == UIInterfaceOrientationLandscapeRight ) ? layoutGuide.leftAnchor : superview.leftAnchor; [constraints addObject: [adView.centerXAnchor constraintEqualToAnchor: anchor constant: bannerHalfHeight]]; } else // CenterRight { NSLayoutAnchor *anchor = ( orientation == UIInterfaceOrientationLandscapeLeft ) ? layoutGuide.rightAnchor : superview.rightAnchor; [constraints addObject: [adView.centerXAnchor constraintEqualToAnchor: anchor constant: -bannerHalfHeight]]; } // Store the ad view with format, so that it can be updated when the orientation changes. self.verticalAdViewFormats[adUnitIdentifier] = adFormat; // If adaptive - make top flexible since we anchor with the bottom of the banner at the edge of the screen if ( !isAdaptiveBannerDisabled ) { adView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; } } } // Otherwise, publisher will likely construct his own views around the adview else { self.safeAreaBackground.hidden = YES; [constraints addObject: [adView.widthAnchor constraintEqualToConstant: adViewWidth]]; if ( [adViewPosition isEqual: @"top_left"] ) { [constraints addObjectsFromArray: @[[adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor constant: adViewOffset.x], [adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor constant: adViewOffset.y]]]; } else if ( [adViewPosition isEqual: @"top_right"] ) { [constraints addObjectsFromArray: @[[adView.topAnchor constraintEqualToAnchor: layoutGuide.topAnchor], [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor]]]; } else if ( [adViewPosition isEqual: @"centered"] ) { [constraints addObjectsFromArray: @[[adView.centerXAnchor constraintEqualToAnchor: layoutGuide.centerXAnchor], [adView.centerYAnchor constraintEqualToAnchor: layoutGuide.centerYAnchor]]]; } else if ( [adViewPosition isEqual: @"bottom_left"] ) { [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor], [adView.leftAnchor constraintEqualToAnchor: superview.leftAnchor]]]; } else if ( [adViewPosition isEqual: @"bottom_right"] ) { [constraints addObjectsFromArray: @[[adView.bottomAnchor constraintEqualToAnchor: layoutGuide.bottomAnchor], [adView.rightAnchor constraintEqualToAnchor: superview.rightAnchor]]]; } } self.adViewConstraints[adUnitIdentifier] = constraints; [NSLayoutConstraint activateConstraints: constraints]; }); } - (UIViewController *)unityViewController { // Handle edge case where `UnityGetGLViewController()` returns nil return UnityGetGLViewController() ?: UnityGetMainWindow().rootViewController ?: [KEY_WINDOW rootViewController]; } - (void)forwardUnityEventWithArgs:(NSDictionary *)args { #if !IS_TEST_APP extern bool _didResignActive; // We should not call any script callbacks when application is not active. Suspend the callback queue if resign is active. // We'll resume the queue once the application becomes active again. self.backgroundCallbackEventsQueue.suspended = _didResignActive; #endif [self.backgroundCallbackEventsQueue addOperationWithBlock:^{ NSString *serializedParameters = [MAUnityAdManager serializeParameters: args]; backgroundCallback(serializedParameters.UTF8String); }]; } + (NSString *)serializeParameters:(NSDictionary *)dict { NSData *jsonData = [NSJSONSerialization dataWithJSONObject: dict options: 0 error: nil]; return [[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding]; } + (NSDictionary *)deserializeParameters:(nullable NSString *)serialized { if ( serialized.length > 0 ) { NSError *error; NSDictionary *deserialized = [NSJSONSerialization JSONObjectWithData: [serialized dataUsingEncoding:NSUTF8StringEncoding] options: 0 error: &error]; if ( error ) { [self log: @"Failed to deserialize (%@) with error %@", serialized, error]; return @{}; } return deserialized; } else { return @{}; } } - (MAAdFormat *)adViewAdFormatForAdUnitIdentifier:(NSString *)adUnitIdentifier { if ( self.adViewAdFormats[adUnitIdentifier] ) { return self.adViewAdFormats[adUnitIdentifier]; } else { return DEVICE_SPECIFIC_ADVIEW_AD_FORMAT; } } - (NSString *)requestLatencyMillisFromRequestLatency:(NSTimeInterval)requestLatency { // Convert latency from seconds to milliseconds to match Android. long requestLatencyMillis = requestLatency * 1000; return @(requestLatencyMillis).stringValue; } #pragma mark - User Service - (void)didDismissUserConsentDialog { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self forwardUnityEventWithArgs: @{@"name" : @"OnSdkConsentDialogDismissedEvent"}]; }); } #pragma mark - CMP Service - (void)showCMPForExistingUser { [self.sdk.cmpService showCMPForExistingUserWithCompletion:^(ALCMPError * _Nullable error) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableDictionary *args = [NSMutableDictionary dictionaryWithCapacity: 2]; args[@"name"] = @"OnCmpCompletedEvent"; if ( error ) { args[@"error"] = @{@"code": @(error.code), @"message": error.message, @"cmpCode": @(error.cmpCode), @"cmpMessage": error.cmpMessage, @"keepInBackground": @(YES)}; } [self forwardUnityEventWithArgs: args]; }); }]; } #pragma mark - Application - (void)applicationPaused:(NSNotification *)notification { [self notifyApplicationStateChangedEventForPauseState: YES]; } - (void)applicationResumed:(NSNotification *)notification { [self notifyApplicationStateChangedEventForPauseState: NO]; } - (void)notifyApplicationStateChangedEventForPauseState:(BOOL)isPaused { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self forwardUnityEventWithArgs: @{@"name": @"OnApplicationStateChanged", @"isPaused": @(isPaused)}]; }); } - (MAAd *)adWithAdUnitIdentifier:(NSString *)adUnitIdentifier { @synchronized ( self.adInfoDictLock ) { return self.adInfoDict[adUnitIdentifier]; } } @end