Add iOS support for TapADN package

This commit is contained in:
2026-06-12 16:05:13 +08:00
parent fd98a7f541
commit e554c34327
8 changed files with 661 additions and 77 deletions

View File

@@ -10,6 +10,7 @@
#import "DirichletMediationUnityBridge.h"
#import <DirichletMediationSDK/DirichletMediationSDK.h>
#import <UIKit/UIKit.h>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
// Unity callback interface
extern "C" void UnitySendMessage(const char* obj, const char* method, const char* msg);
@@ -119,6 +120,88 @@ static NSDictionary* ParseExtras(const char* extrasJson) {
return error ? nil : dict;
}
static void SendLoadErrorToUnity(NSString* handleId, NSString* adType, NSError* error, NSString* fallbackMessage) {
NSInteger code = error ? error.code : -1;
NSString* message = error.localizedDescription ?: fallbackMessage ?: @"Unknown error";
NSDictionary* errorData = @{
@"code": @(code),
@"message": message
};
SendLoadCallbackToUnity(handleId, @"load_error", adType, errorData);
}
static void SendShowErrorToUnity(NSString* handleId, NSString* adType, NSInteger code, NSString* message) {
NSDictionary* errorData = @{
@"code": @(code),
@"message": message ?: @"Ad failed to show"
};
SendEventToUnity(handleId, @"show_error", adType ?: @"unknown", errorData);
}
static UIViewController* TopViewController(UIViewController* rootViewController) {
UIViewController* top = rootViewController;
while (top.presentedViewController) {
top = top.presentedViewController;
}
if ([top isKindOfClass:[UINavigationController class]]) {
return TopViewController(((UINavigationController*)top).visibleViewController);
}
if ([top isKindOfClass:[UITabBarController class]]) {
return TopViewController(((UITabBarController*)top).selectedViewController);
}
return top;
}
static UIViewController* CurrentRootViewController(void) {
UIWindow* keyWindow = nil;
if (@available(iOS 13.0, *)) {
for (UIScene* scene in [UIApplication sharedApplication].connectedScenes) {
if (scene.activationState != UISceneActivationStateForegroundActive ||
![scene isKindOfClass:[UIWindowScene class]]) {
continue;
}
UIWindowScene* windowScene = (UIWindowScene*)scene;
for (UIWindow* window in windowScene.windows) {
if (window.isKeyWindow) {
keyWindow = window;
break;
}
}
if (keyWindow) {
break;
}
}
}
if (!keyWindow) {
keyWindow = [UIApplication sharedApplication].keyWindow;
}
return TopViewController(keyWindow.rootViewController);
}
static NSString* AdTypeForObject(id ad) {
if ([ad isKindOfClass:[DRMRewardVideoAd class]]) {
return @"reward_video";
}
if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
return @"interstitial";
}
if ([ad isKindOfClass:[DRMBannerAd class]]) {
return @"banner";
}
if ([ad isKindOfClass:[DRMSplashAd class]]) {
return @"splash";
}
return @"unknown";
}
#pragma mark - Ad Instance Manager
@interface DirichletMediationInstanceManager : NSObject
@@ -336,6 +419,7 @@ bool DirichletMediationUnityBridge_Initialize(
// Check if SDK is already initialized
if ([DirichletMediation isInitialized]) {
NSLog(@"[DirichletMediationUnityBridge] SDK already initialized");
SendInitCallbackToUnity(YES, nil, @"already_initialized");
return true;
}
@@ -388,8 +472,29 @@ bool DirichletMediationUnityBridge_Initialize(
}
void DirichletMediationUnityBridge_RequestPermissionIfNeeded(void) {
// iOS 14+ ATT permission is handled internally by the SDK
NSLog(@"[DirichletMediationUnityBridge] RequestPermissionIfNeeded called");
if (@available(iOS 14.0, *)) {
void (^requestBlock)(void) = ^{
ATTrackingManagerAuthorizationStatus status = [ATTrackingManager trackingAuthorizationStatus];
if (status != ATTrackingManagerAuthorizationStatusNotDetermined) {
NSLog(@"[DirichletMediationUnityBridge] ATT status already determined: %lu", (unsigned long)status);
return;
}
[ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
NSLog(@"[DirichletMediationUnityBridge] ATT request completed with status: %lu", (unsigned long)status);
}];
};
if ([NSThread isMainThread]) {
requestBlock();
} else {
dispatch_async(dispatch_get_main_queue(), requestBlock);
}
} else {
NSLog(@"[DirichletMediationUnityBridge] ATT is not required below iOS 14");
}
}
const char* DirichletMediationUnityBridge_GetSdkVersion(void) {
@@ -438,11 +543,7 @@ const char* DirichletMediationUnityBridge_LoadRewardVideoAd(long long spaceId, c
SendLoadCallbackToUnity(handleId, @"load_success", @"reward_video", nil);
} else {
NSLog(@"[DirichletMediationUnityBridge] RewardVideoAd load failed: %@", error.localizedDescription);
NSDictionary* errorData = @{
@"code": @(error.code),
@"message": error.localizedDescription ?: @"Unknown error"
};
SendLoadCallbackToUnity(handleId, @"load_error", @"reward_video", errorData);
SendLoadErrorToUnity(handleId, @"reward_video", error, @"RewardVideoAd load failed");
}
}];
@@ -475,11 +576,7 @@ const char* DirichletMediationUnityBridge_LoadInterstitialAd(long long spaceId,
SendLoadCallbackToUnity(handleId, @"load_success", @"interstitial", nil);
} else {
NSLog(@"[DirichletMediationUnityBridge] InterstitialAd load failed: %@", error.localizedDescription);
NSDictionary* errorData = @{
@"code": @(error.code),
@"message": error.localizedDescription ?: @"Unknown error"
};
SendLoadCallbackToUnity(handleId, @"load_error", @"interstitial", errorData);
SendLoadErrorToUnity(handleId, @"interstitial", error, @"InterstitialAd load failed");
}
}];
@@ -520,11 +617,7 @@ const char* DirichletMediationUnityBridge_LoadBannerAd(long long spaceId, const
SendLoadCallbackToUnity(handleId, @"load_success", @"banner", nil);
} else {
NSLog(@"[DirichletMediationUnityBridge] BannerAd load failed: %@", error.localizedDescription);
NSDictionary* errorData = @{
@"code": @(error.code),
@"message": error.localizedDescription ?: @"Unknown error"
};
SendLoadCallbackToUnity(handleId, @"load_error", @"banner", errorData);
SendLoadErrorToUnity(handleId, @"banner", error, @"BannerAd load failed");
}
}];
@@ -565,11 +658,7 @@ const char* DirichletMediationUnityBridge_LoadSplashAd(long long spaceId, const
SendLoadCallbackToUnity(handleId, @"load_success", @"splash", nil);
} else {
NSLog(@"[DirichletMediationUnityBridge] SplashAd load failed: %@", error.localizedDescription);
NSDictionary* errorData = @{
@"code": @(error.code),
@"message": error.localizedDescription ?: @"Unknown error"
};
SendLoadCallbackToUnity(handleId, @"load_error", @"splash", errorData);
SendLoadErrorToUnity(handleId, @"splash", error, @"SplashAd load failed");
}
}];
@@ -584,12 +673,16 @@ bool DirichletMediationUnityBridge_ShowAd(const char* handleId, const char* extr
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not found for handle %@", nsHandleId);
return false;
}
// Ensure show is always called on main thread (aligned with Ad Unity implementation)
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController* rootVC = [[[UIApplication sharedApplication] keyWindow] rootViewController];
__block BOOL didStartShow = NO;
NSString* adType = AdTypeForObject(ad);
NSDictionary* extrasDict = ParseExtras(extras);
void (^showBlock)(void) = ^{
UIViewController* rootVC = CurrentRootViewController();
if (!rootVC) {
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Root view controller not found");
SendShowErrorToUnity(nsHandleId, adType, -2, @"Root view controller not found");
return;
}
@@ -597,52 +690,86 @@ bool DirichletMediationUnityBridge_ShowAd(const char* handleId, const char* extr
DRMRewardVideoAd* rewardAd = (DRMRewardVideoAd*)ad;
if ([rewardAd isReady]) {
[rewardAd showFromViewController:rootVC];
didStartShow = YES;
NSLog(@"[DirichletMediationUnityBridge] Showing reward video ad: %@", nsHandleId);
} else {
SendShowErrorToUnity(nsHandleId, @"reward_video", -3, @"Reward video ad is not ready");
}
} else if ([ad isKindOfClass:[DRMInterstitialAd class]]) {
DRMInterstitialAd* interstitialAd = (DRMInterstitialAd*)ad;
if ([interstitialAd isReady]) {
[interstitialAd showFromViewController:rootVC];
didStartShow = YES;
NSLog(@"[DirichletMediationUnityBridge] Showing interstitial ad: %@", nsHandleId);
} else {
SendShowErrorToUnity(nsHandleId, @"interstitial", -3, @"Interstitial ad is not ready");
}
} else if ([ad isKindOfClass:[DRMBannerAd class]]) {
DRMBannerAd* bannerAd = (DRMBannerAd*)ad;
UIView* bannerView = bannerAd.view;
if (bannerView) {
// Banner 广告需要将 view 添加到视图控制器上
// 注意Unity 侧需要通过 Unity UI 系统来处理 Banner 视图
// 这里我们发送一个事件通知 Unity 侧,让 Unity 侧来处理视图的展示
// 或者直接将视图添加到根视图控制器上(临时方案)
[bannerView removeFromSuperview];
[rootVC.view addSubview:bannerView];
bannerView.translatesAutoresizingMaskIntoConstraints = NO;
// 设置约束,让 Banner 显示在底部
NSInteger baseline = [extrasDict[@"banner_baseline"] integerValue];
CGFloat offset = extrasDict[@"banner_offset"] ? [extrasDict[@"banner_offset"] floatValue] : 0;
NSLayoutYAxisAnchor* verticalAnchor = baseline == 0
? rootVC.view.safeAreaLayoutGuide.topAnchor
: rootVC.view.safeAreaLayoutGuide.bottomAnchor;
NSLayoutConstraint* verticalConstraint = baseline == 0
? [bannerView.topAnchor constraintEqualToAnchor:verticalAnchor constant:offset]
: [bannerView.bottomAnchor constraintEqualToAnchor:verticalAnchor constant:-offset];
[NSLayoutConstraint activateConstraints:@[
[bannerView.leadingAnchor constraintEqualToAnchor:rootVC.view.leadingAnchor],
[bannerView.trailingAnchor constraintEqualToAnchor:rootVC.view.trailingAnchor],
[bannerView.bottomAnchor constraintEqualToAnchor:rootVC.view.safeAreaLayoutGuide.bottomAnchor],
[bannerView.centerXAnchor constraintEqualToAnchor:rootVC.view.centerXAnchor],
[bannerView.widthAnchor constraintLessThanOrEqualToAnchor:rootVC.view.widthAnchor],
verticalConstraint,
[bannerView.heightAnchor constraintEqualToConstant:bannerAd.size.height > 0 ? bannerAd.size.height : 50]
]];
didStartShow = YES;
NSLog(@"[DirichletMediationUnityBridge] Showing banner ad: %@", nsHandleId);
} else {
NSLog(@"[DirichletMediationUnityBridge] Banner ad view not available: %@", nsHandleId);
SendShowErrorToUnity(nsHandleId, @"banner", -4, @"Banner ad view not available");
}
} else if ([ad isKindOfClass:[DRMSplashAd class]]) {
DRMSplashAd* splashAd = (DRMSplashAd*)ad;
if ([splashAd isReady]) {
[splashAd showFromViewController:rootVC];
didStartShow = YES;
NSLog(@"[DirichletMediationUnityBridge] Showing splash ad: %@", nsHandleId);
} else {
SendShowErrorToUnity(nsHandleId, @"splash", -3, @"Splash ad is not ready");
}
} else {
NSLog(@"[DirichletMediationUnityBridge] ShowAd failed: Ad not ready or unknown type");
SendShowErrorToUnity(nsHandleId, @"unknown", -5, @"Unknown ad type");
}
});
};
if ([NSThread isMainThread]) {
showBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), showBlock);
}
return true;
return didStartShow;
}
void DirichletMediationUnityBridge_DestroyAd(const char* handleId) {
NSString* nsHandleId = CreateNSString(handleId);
NSLog(@"[DirichletMediationUnityBridge] Destroying ad: %@", nsHandleId);
id ad = [[DirichletMediationInstanceManager shared] adForHandle:nsHandleId];
if ([ad isKindOfClass:[DRMBannerAd class]]) {
DRMBannerAd* bannerAd = (DRMBannerAd*)ad;
if (bannerAd.view.superview) {
dispatch_async(dispatch_get_main_queue(), ^{
[bannerAd.view removeFromSuperview];
});
}
}
// Remove ad instance
[[DirichletMediationInstanceManager shared] removeAdForHandle:nsHandleId];
@@ -679,4 +806,3 @@ bool DirichletMediationUnityBridge_IsAdValid(const char* handleId) {
}
} // extern "C"