2026-04-20 13:49:36 +08:00
//
// MAUnityAdManager.m
// AppLovin MAX Unity Plugin
//
# import "MAUnityAdManager.h"
# 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
extern bool max_unity_should_disable_all_logs ( void ) ; // Forward declaration
// 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
2026-05-08 11:03:00 +08:00
@interface MAUnityAdManager ( ) < MAAdDelegate , MAAdViewAdDelegate , MARewardedAdDelegate , MAAdRevenueDelegate , MAAdReviewDelegate , MAAdExpirationDelegate >
2026-04-20 13:49:36 +08:00
// Parent Fields
@property ( nonatomic , weak ) ALSdk * sdk ;
// Fullscreen Ad Fields
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MAInterstitialAd * > * interstitials ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MAAppOpenAd * > * appOpenAds ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MARewardedAd * > * rewardedAds ;
// AdView Fields
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MAAdView * > * adViews ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MAAdFormat * > * adViewAdFormats ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSString * > * adViewPositions ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSValue * > * adViewOffsets ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSNumber * > * adViewWidths ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MAAdFormat * > * verticalAdViewFormats ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSArray < NSLayoutConstraint * > * > * adViewConstraints ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSMutableDictionary < NSString * , NSString * > * > * adViewExtraParametersToSetAfterCreate ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSMutableDictionary < NSString * , id > * > * adViewLocalExtraParametersToSetAfterCreate ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , NSString * > * adViewCustomDataToSetAfterCreate ;
@property ( nonatomic , strong ) NSMutableArray < NSString * > * adUnitIdentifiersToShowAfterCreate ;
@property ( nonatomic , strong ) NSMutableSet < NSString * > * disabledAdaptiveBannerAdUnitIdentifiers ;
@property ( nonatomic , strong ) NSMutableSet < NSString * > * disabledAutoRefreshAdViewAdUnitIdentifiers ;
2026-05-08 11:03:00 +08:00
@property ( nonatomic , strong ) NSMutableSet < NSString * > * ignoreSafeAreaLandscapeAdUnitIdentifiers ;
2026-04-20 13:49:36 +08:00
@property ( nonatomic , strong ) UIView * safeAreaBackground ;
@property ( nonatomic , strong , nullable ) UIColor * publisherBannerBackgroundColor ;
@property ( nonatomic , strong ) NSMutableDictionary < NSString * , MAAd * > * 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 . 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 ] ;
2026-05-08 11:03:00 +08:00
self . ignoreSafeAreaLandscapeAdUnitIdentifiers = [ NSMutableSet setWithCapacity : 2 ] ;
2026-04-20 13:49:36 +08:00
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 ;
}
+ ( void ) setUnityBackgroundCallback: ( ALUnityBackgroundCallback ) unityBackgroundCallback
{
backgroundCallback = unityBackgroundCallback ;
}
# pragma mark - Plugin Initialization
- ( void ) initializeSdkWithConfiguration: ( ALSdkInitializationConfiguration * ) initConfig andCompletionHandler: ( ALSdkInitializationCompletionHandler ) completionHandler ;
{
self . sdk = [ ALSdk shared ] ;
[ self . sdk initializeWithConfiguration : initConfig completionHandler : ^ ( 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 ] ) } ] ;
} ) ;
} ] ;
}
# pragma mark - Banners
2026-05-08 11:03:00 +08:00
- ( void ) createBannerWithAdUnitIdentifier : ( nullable NSString * ) adUnitIdentifier atPosition : ( nullable NSString * ) bannerPosition isAdaptive : ( BOOL ) isAdaptive
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
[ self createAdViewWithAdUnitIdentifier : adUnitIdentifier adFormat : [ self adViewAdFormatForAdUnitIdentifier : adUnitIdentifier ] atPosition : bannerPosition withOffset : CGPointZero isAdaptive : isAdaptive ] ;
2026-04-20 13:49:36 +08:00
}
2026-05-08 11:03:00 +08:00
- ( void ) createBannerWithAdUnitIdentifier : ( nullable NSString * ) adUnitIdentifier x : ( CGFloat ) xOffset y : ( CGFloat ) yOffset isAdaptive : ( BOOL ) isAdaptive
2026-04-20 13:49:36 +08:00
{
2026-05-08 11:03:00 +08:00
[ self createAdViewWithAdUnitIdentifier : adUnitIdentifier adFormat : [ self adViewAdFormatForAdUnitIdentifier : adUnitIdentifier ] atPosition : DEFAULT_AD_VIEW_POSITION withOffset : CGPointMake ( xOffset , yOffset ) isAdaptive : isAdaptive ] ;
2026-04-20 13:49:36 +08:00
}
- ( 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
{
2026-05-08 11:03:00 +08:00
[ self createAdViewWithAdUnitIdentifier : adUnitIdentifier adFormat : MAAdFormat . mrec atPosition : mrecPosition withOffset : CGPointZero isAdaptive : NO ] ;
2026-04-20 13:49:36 +08:00
}
- ( void ) createMRecWithAdUnitIdentifier : ( nullable NSString * ) adUnitIdentifier x : ( CGFloat ) xOffset y : ( CGFloat ) yOffset
{
2026-05-08 11:03:00 +08:00
[ self createAdViewWithAdUnitIdentifier : adUnitIdentifier adFormat : MAAdFormat . mrec atPosition : DEFAULT_AD_VIEW_POSITION withOffset : CGPointMake ( xOffset , yOffset ) isAdaptive : NO ] ;
2026-04-20 13:49:36 +08:00
}
- ( 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 - Event Tracking
- ( void ) trackEvent : ( nullable NSString * ) event parameters : ( nullable NSString * ) parameters
{
NSDictionary < NSString * , id > * deserializedParameters = [ MAUnityAdManager deserializeParameters : parameters ] ;
[ self . sdk . eventService trackEvent : event parameters : deserializedParameters ] ;
}
# pragma mark - Ad Info
- ( NSDictionary < NSString * , id > * ) 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 < NSString * , id > * ) createAdWaterfallInfo : ( MAAdWaterfallInfo * ) waterfallInfo
{
NSMutableDictionary < NSString * , NSObject * > * waterfallInfoDict = [ NSMutableDictionary dictionary ] ;
if ( ! waterfallInfo ) return waterfallInfoDict ;
waterfallInfoDict [ @" name " ] = waterfallInfo . name ;
waterfallInfoDict [ @" testName " ] = waterfallInfo . testName ;
NSMutableArray < NSDictionary < NSString * , NSObject * > * > * 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 < NSString * , id > * ) createNetworkResponseInfo : ( MANetworkResponseInfo * ) response
{
NSMutableDictionary < NSString * , NSObject * > * networkResponseDict = [ NSMutableDictionary dictionary ] ;
networkResponseDict [ @" adLoadState " ] = @( response . adLoadState ) . stringValue ;
MAMediatedNetworkInfo * mediatedNetworkInfo = response . mediatedNetwork ;
if ( mediatedNetworkInfo )
{
NSMutableDictionary < NSString * , NSObject * > * networkInfoObject = [ NSMutableDictionary dictionary ] ;
networkInfoObject [ @" name " ] = response . mediatedNetwork . name ;
networkInfoObject [ @" adapterClassName " ] = response . mediatedNetwork . adapterClassName ;
networkInfoObject [ @" adapterVersion " ] = response . mediatedNetwork . adapterVersion ;
networkInfoObject [ @" sdkVersion " ] = response . mediatedNetwork . sdkVersion ;
2026-05-08 11:03:00 +08:00
networkInfoObject [ @" initializationStatus " ] = @( response . mediatedNetwork . initializationStatus ) ;
2026-04-20 13:49:36 +08:00
networkResponseDict [ @" mediatedNetwork " ] = networkInfoObject ;
}
networkResponseDict [ @" credentials " ] = response . credentials ;
networkResponseDict [ @" isBidding " ] = @( [ response isBidding ] ) ;
MAError * error = response . error ;
if ( error )
{
NSMutableDictionary < NSString * , NSObject * > * 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
{
[ self logInvalidAdFormat : adFormat ] ;
return ;
}
dispatch_async ( dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ) , ^ {
@synchronized ( self . adInfoDictLock )
{
self . adInfoDict [ ad . adUnitIdentifier ] = ad ;
}
NSDictionary < NSString * , id > * 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
{
[ 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
{
[ self logInvalidAdFormat : adFormat ] ;
return ;
}
NSDictionary < NSString * , id > * 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 " ;
}
2026-05-08 11:03:00 +08:00
else // rewarded
2026-04-20 13:49:36 +08:00
{
name = @" OnRewardedAdDisplayedEvent " ;
}
NSDictionary < NSString * , id > * 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 " ;
}
2026-05-08 11:03:00 +08:00
else // rewarded
2026-04-20 13:49:36 +08:00
{
name = @" OnRewardedAdFailedToDisplayEvent " ;
}
NSMutableDictionary < NSString * , id > * 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 " ;
}
2026-05-08 11:03:00 +08:00
else // rewarded
2026-04-20 13:49:36 +08:00
{
name = @" OnRewardedAdHiddenEvent " ;
}
NSDictionary < NSString * , id > * 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 < NSString * , id > * 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 < NSString * , id > * args = [ self defaultAdEventParametersForName : name withAd : ad ] ;
[ self forwardUnityEventWithArgs : args ] ;
} ) ;
}
- ( void ) didRewardUserForAd : ( MAAd * ) ad withReward : ( MAReward * ) reward
{
dispatch_async ( dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ) , ^ {
MAAdFormat * adFormat = ad . format ;
2026-05-08 11:03:00 +08:00
if ( adFormat ! = MAAdFormat . rewarded )
2026-04-20 13:49:36 +08:00
{
[ self logInvalidAdFormat : adFormat ] ;
return ;
}
NSString * rewardLabel = reward ? reward . label : @" " ;
NSInteger rewardAmountInt = reward ? reward . amount : 0 ;
NSString * rewardAmount = [ @( rewardAmountInt ) stringValue ] ;
2026-05-08 11:03:00 +08:00
NSString * name = @" OnRewardedAdReceivedRewardEvent " ;
2026-04-20 13:49:36 +08:00
NSMutableDictionary < NSString * , id > * 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
{
[ self logInvalidAdFormat : adFormat ] ;
return ;
}
NSMutableDictionary < NSString * , id > * args = [ self defaultAdEventParametersForName : name withAd : ad ] ;
args [ @" keepInBackground " ] = @( [ adFormat isFullscreenAd ] ) ;
[ self forwardUnityEventWithArgs : args ] ;
} ) ;
}
2026-05-08 11:03:00 +08:00
- ( void ) didReloadExpiredAd : ( MAAd * ) expiredAd withNewAd : ( MAAd * ) newAd ;
{
dispatch_async ( dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ) , ^ {
NSString * name ;
MAAdFormat * adFormat = newAd . format ;
if ( MAAdFormat . interstitial = = adFormat )
{
name = @" OnExpiredInterstitialAdReloadedEvent " ;
}
else if ( MAAdFormat . appOpen = = adFormat )
{
name = @" OnExpiredAppOpenAdReloadedEvent " ;
}
else if ( MAAdFormat . rewarded = = adFormat )
{
name = @" OnExpiredRewardedAdReloadedEvent " ;
}
else
{
[ self logInvalidAdFormat : adFormat ] ;
return ;
}
@synchronized ( self . adInfoDictLock )
{
self . adInfoDict [ newAd . adUnitIdentifier ] = newAd ;
}
NSMutableDictionary < NSString * , NSObject * > * args = [ NSMutableDictionary dictionary ] ;
args [ @" expiredAdInfo " ] = [ self adInfoForAd : expiredAd ] ;
args [ @" newAdInfo " ] = [ self adInfoForAd : newAd ] ;
args [ @" name " ] = name ;
[ self forwardUnityEventWithArgs : args ] ;
} ) ;
}
2026-04-20 13:49:36 +08:00
- ( 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
{
[ self logInvalidAdFormat : adFormat ] ;
return ;
}
NSMutableDictionary < NSString * , id > * 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 < NSString * , id > * ) defaultAdEventParametersForName : ( NSString * ) name withAd : ( MAAd * ) ad
{
NSMutableDictionary < NSString * , id > * args = [ [ self adInfoForAd : ad ] mutableCopy ] ;
args [ @" name " ] = name ;
return args ;
}
# pragma mark - Internal Methods
2026-05-08 11:03:00 +08:00
- ( void ) createAdViewWithAdUnitIdentifier : ( NSString * ) adUnitIdentifier adFormat : ( MAAdFormat * ) adFormat atPosition : ( NSString * ) adViewPosition withOffset : ( CGPoint ) offset isAdaptive : ( BOOL ) isAdaptive
2026-04-20 13:49:36 +08:00
{
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
2026-05-08 11:03:00 +08:00
MAAdView * adView = [ self retrieveAdViewForAdUnitIdentifier : adUnitIdentifier adFormat : adFormat atPosition : adViewPosition withOffset : offset isAdaptive : isAdaptive ] ;
2026-04-20 13:49:36 +08:00
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 < NSString * , NSString * > * extraParameters = self . adViewExtraParametersToSetAfterCreate [ adUnitIdentifier ] ;
// 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 < NSString * , NSString * > * 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 < NSString * , NSString * > * 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 < NSString * , id > * 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 ] )
{
2026-05-08 11:03:00 +08:00
[ self log : @" Setting adaptive banners via extra parameters is deprecated and will be removed in a future plugin version. Use the CreateBanner(adUnitIdentifier, AdViewConfiguration) API to properly configure adaptive banners. " ] ;
2026-04-20 13:49:36 +08:00
BOOL shouldUseAdaptiveBanner = [ NSNumber al_numberWithString : value ] . boolValue ;
if ( shouldUseAdaptiveBanner )
{
[ self . disabledAdaptiveBannerAdUnitIdentifiers removeObject : adUnitIdentifier ] ;
}
else
{
[ self . disabledAdaptiveBannerAdUnitIdentifiers addObject : adUnitIdentifier ] ;
}
[ self positionAdViewForAdUnitIdentifier : adUnitIdentifier adFormat : adFormat ] ;
}
2026-05-08 11:03:00 +08:00
else if ( [ @" ignore_safe_area_landscape " isEqualToString : key ] & & [ NSNumber al_numberWithString : value ] . boolValue )
{
[ self . ignoreSafeAreaLandscapeAdUnitIdentifiers addObject : adUnitIdentifier ] ;
[ self positionAdViewForAdUnitIdentifier : adUnitIdentifier adFormat : adFormat ] ;
}
}
if ( [ adFormat isAdViewAd ] & & [ @" clips_to_bounds " isEqualToString : key ] )
{
BOOL clipsToBounds = [ NSNumber al_numberWithString : value ] . boolValue ;
MAAdView * view = [ self retrieveAdViewForAdUnitIdentifier : adUnitIdentifier adFormat : adFormat ] ;
view . clipsToBounds = clipsToBounds ;
2026-04-20 13:49:36 +08:00
}
}
- ( 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 , . . .
{
if ( max_unity_should_disable_all_logs ( ) ) return ;
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 , . . .
{
if ( max_unity_should_disable_all_logs ( ) ) return ;
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 )
{
2026-05-08 11:03:00 +08:00
result = [ [ MAInterstitialAd alloc ] initWithAdUnitIdentifier : adUnitIdentifier ] ;
2026-04-20 13:49:36 +08:00
result . delegate = self ;
result . revenueDelegate = self ;
result . adReviewDelegate = self ;
2026-05-08 11:03:00 +08:00
result . expirationDelegate = self ;
2026-04-20 13:49:36 +08:00
self . interstitials [ adUnitIdentifier ] = result ;
}
return result ;
}
- ( MAAppOpenAd * ) retrieveAppOpenAdForAdUnitIdentifier : ( NSString * ) adUnitIdentifier
{
MAAppOpenAd * result = self . appOpenAds [ adUnitIdentifier ] ;
if ( ! result )
{
2026-05-08 11:03:00 +08:00
result = [ [ MAAppOpenAd alloc ] initWithAdUnitIdentifier : adUnitIdentifier ] ;
2026-04-20 13:49:36 +08:00
result . delegate = self ;
result . revenueDelegate = self ;
2026-05-08 11:03:00 +08:00
result . expirationDelegate = self ;
2026-04-20 13:49:36 +08:00
self . appOpenAds [ adUnitIdentifier ] = result ;
}
return result ;
}
- ( MARewardedAd * ) retrieveRewardedAdForAdUnitIdentifier : ( NSString * ) adUnitIdentifier
{
MARewardedAd * result = self . rewardedAds [ adUnitIdentifier ] ;
if ( ! result )
{
2026-05-08 11:03:00 +08:00
result = [ MARewardedAd sharedWithAdUnitIdentifier : adUnitIdentifier ] ;
2026-04-20 13:49:36 +08:00
result . delegate = self ;
result . revenueDelegate = self ;
result . adReviewDelegate = self ;
2026-05-08 11:03:00 +08:00
result . expirationDelegate = self ;
2026-04-20 13:49:36 +08:00
self . rewardedAds [ adUnitIdentifier ] = result ;
}
return result ;
}
- ( MAAdView * ) retrieveAdViewForAdUnitIdentifier : ( NSString * ) adUnitIdentifier adFormat : ( MAAdFormat * ) adFormat
{
2026-05-08 11:03:00 +08:00
return [ self retrieveAdViewForAdUnitIdentifier : adUnitIdentifier adFormat : adFormat atPosition : nil withOffset : CGPointZero isAdaptive : YES ] ;
2026-04-20 13:49:36 +08:00
}
2026-05-08 11:03:00 +08:00
- ( MAAdView * ) retrieveAdViewForAdUnitIdentifier : ( NSString * ) adUnitIdentifier adFormat : ( MAAdFormat * ) adFormat atPosition : ( NSString * ) adViewPosition withOffset : ( CGPoint ) offset isAdaptive : ( BOOL ) isAdaptive
2026-04-20 13:49:36 +08:00
{
MAAdView * result = self . adViews [ adUnitIdentifier ] ;
if ( ! result & & adViewPosition )
2026-05-08 11:03:00 +08:00
{
MAAdViewConfiguration * config = [ MAAdViewConfiguration configurationWithBuilderBlock : ^ ( MAAdViewConfigurationBuilder * builder ) {
// Set adaptive type only for banner ads. If adaptive is enabled, use ANCHORED; otherwise, fall back to NONE.
if ( [ adFormat isBannerOrLeaderAd ] )
{
if ( isAdaptive )
{
builder . adaptiveType = MAAdViewAdaptiveTypeAnchored ;
}
else
{
builder . adaptiveType = MAAdViewAdaptiveTypeNone ;
[ self . disabledAdaptiveBannerAdUnitIdentifiers addObject : adUnitIdentifier ] ;
}
}
} ] ;
2026-04-20 13:49:36 +08:00
// 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.
2026-05-08 11:03:00 +08:00
result = [ [ MAAdView alloc ] initWithAdUnitIdentifier : adUnitIdentifier adFormat : adFormat configuration : config ] ;
2026-04-20 13:49:36 +08:00
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 < NSLayoutConstraint * > * 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 < NSLayoutConstraint * > * constraints = [ NSMutableArray arrayWithObject : [ adView . heightAnchor constraintEqualToConstant : adViewSize . height ] ] ;
UILayoutGuide * layoutGuide = superview . safeAreaLayoutGuide ;
2026-05-08 11:03:00 +08:00
BOOL shouldIgnoreSafeArea = [ self shouldIgnoreSafeAreaForAdUnitIdentifier : adUnitIdentifier ] ;
NSLayoutAnchor * topAnchor = shouldIgnoreSafeArea ? superview . topAnchor : layoutGuide . topAnchor ;
2026-04-20 13:49:36 +08:00
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 " ] )
{
2026-05-08 11:03:00 +08:00
[ constraints addObjectsFromArray : @[ [ adView . topAnchor constraintEqualToAnchor : topAnchor ] ,
2026-04-20 13:49:36 +08:00
[ 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 " ] )
{
2026-05-08 11:03:00 +08:00
[ constraints addObjectsFromArray : @[ [ adView . topAnchor constraintEqualToAnchor : topAnchor ] ,
2026-04-20 13:49:36 +08:00
[ 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 " ] )
{
2026-05-08 11:03:00 +08:00
[ constraints addObject : [ adView . topAnchor constraintEqualToAnchor : topAnchor ] ] ;
2026-04-20 13:49:36 +08:00
}
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 ] ,
2026-05-08 11:03:00 +08:00
[ adView . topAnchor constraintEqualToAnchor : topAnchor constant : adViewOffset . y ] ] ] ;
2026-04-20 13:49:36 +08:00
}
else if ( [ adViewPosition isEqual : @" top_right " ] )
{
2026-05-08 11:03:00 +08:00
[ constraints addObjectsFromArray : @[ [ adView . topAnchor constraintEqualToAnchor : topAnchor ] ,
2026-04-20 13:49:36 +08:00
[ 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 < NSString * , id > * ) 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 < NSString * , id > * ) dict
{
NSData * jsonData = [ NSJSONSerialization dataWithJSONObject : dict options : 0 error : nil ] ;
return [ [ NSString alloc ] initWithData : jsonData encoding : NSUTF8StringEncoding ] ;
}
+ ( NSDictionary < NSString * , id > * ) deserializeParameters : ( nullable NSString * ) serialized
{
if ( serialized . length > 0 )
{
NSError * error ;
NSDictionary < NSString * , id > * 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 < NSString * , id > * 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 ] ;
}
}
2026-05-08 11:03:00 +08:00
# pragma mark - Helper
- ( BOOL ) shouldIgnoreSafeAreaForAdUnitIdentifier : ( NSString * ) adUnitIdentifier
{
if ( ! [ self . ignoreSafeAreaLandscapeAdUnitIdentifiers containsObject : adUnitIdentifier ] ) return NO ;
// We should use the superview instead of layout guide if the application's orientation is landscape and the extra parameter is set
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
return orientation = = UIInterfaceOrientationLandscapeRight | | orientation = = UIInterfaceOrientationLandscapeLeft ;
}
2026-04-20 13:49:36 +08:00
@end