这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战。
引言
本文以对接开屏广告为例子
developers.adnet.qq.com/doc/ios/uni…
| 广告类型 | 
接入方式 | 
简介 | 
适用场景 | 
版本备注 | 
| 开屏广告 | 
SDK原生渲染 | 
开屏广告以App启动作为曝光时机,提供5s的可感知广告展示。用户可以点击广告跳转到目标页面;或者点击右上角的“跳过”按钮,跳转到app内容首页。  开屏V+广告是一个5s-30s的视频广告,在5s开屏呈现的过程中,用户点击右上角的“进入首页”或5s曝光结束后,视频均将收缩到APP内右下角的小视窗继续播放。 | 
APP启动时 | 
包含 开屏 与 开屏V+ 两种 | 
 | 
 | 
 | 
 | 
 | 
IOS对接文档:
developers.adnet.qq.com/doc/ios/gui…
ios对接SDK包:对接demo 工程
OC 版本:github.com/zhangkn/GDT…,更多资源请关注公众号:iOS技能
d3g.qq.com/gdt/sdk/ios…

优量汇 iOS14 适配指南
developers.adnet.qq.com/doc/ios/uni…
前提使用优量汇iOS SDK 4.12.5及以上版本
本文重点是拉取最新SDK库、iOS14的idfa适配以及iOS13的modal样式适配
I 、 SDK部署
developers.adnet.qq.com/doc/ios/gui…
1.1 术语介绍
APPID:媒体 ID,是您在腾讯优量汇开发者平台创建媒体时获得的ID,这个ID是我们在广告网络中识别您应用的唯一ID。
PlacementId:广告位 ID,是您在腾讯优量汇开发者平台为您的应用所创建的某种类型(Banner、开屏、插屏、平台模板、激励视频)的广告位置的ID。
dto(Data Transfer Objects): 接口返回的原始数据
权限申请
部分广告样式的接入需要权限,您可以联系腾讯优量汇运营进行了解和权限申请。在腾讯优量汇开发者平台新建广告位时您只能看到您有相应权限的广告位类型。目前有专门的优量汇代理商负责运营和商务。
说明:针对单媒体的用户,允许获取idfa和定位权限的,投放定向广告;不允许获取权限的用户,投放通投广告,媒体可以选择是否把idfa和定位数据提供给优量汇,并承担相应广告填充和eCPM单价下降损失的结果。
未在优量汇注册,请注册加入优量汇或者申请成为运营者
运营者adnet.qq.com/register/be…
注册:adnet.qq.com/register
1.2  拉取最新SDK库
pod 'GDTMobSDK'
#-> Installing GDTMobSDK 4.12.90 (was 4.11.11)
使用pod update GDTMobSDK  --verbose  拉取最新库,否则无法更新成功SDK到项目
➜  retail git:(develop) ✗ pod update GDTMobSDK  --verbose
1.3 接入注意事项
目前开屏广告只针对iPhone设备在垂直方向上展示。
- 开屏全屏广告需使得显示区域其高度与设备高度一致,即为开屏全屏广告。开发者可通过以下接口配合使用提供媒体logo,用以随开屏广告展示。
 
logo 推荐使用透明背景色,可为空
 - (void)loadFullScreenAd;
 - (void)showFullScreenAdInWindow:(UIWindow *)window withLogoImage:(UIImage *)logoImage skipView:(UIView *)skipView;
- 
开屏半屏广告的显示区域其高度一定要大于设备高度的75%(建议值大于80%),最小高度要大于400dp,开屏广告默认只在竖屏展示,横屏一般不满足尺寸要求。
 
- 
优量汇开屏广告支持预加载开屏广告,调用方法如下:
 
    GDTSplashAd *preloadSplashAd = [[GDTSplashAd alloc] initWithPlacementId:YOUR_PLACEMENT_ID];
    [preloadSplashAd preloadSplashOrderWithPlacementId:YOUR_PLACEMENT_ID];
- 初始化SDK,加载广告的代码推荐放在
didFinishLaunchingWithOptions的第一行 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [self setupGDTSDKConfig];
    ....
    return YES;
}
- 由于SDK的静态库文件libGDTMobSDK.a>110M, 提交到git 仓库时可能由于文件大小的限制导致提交失败,所以推荐你使用.gitignore 将Pods目录忽略。其他同事拉取代码之后,可采用
pod install --verbose --no-repo-update只安装新添加的库,已更新的库忽略。或者更新指定的库,其它库忽略 pod update 库名 --verbose --no-repo-update
 
# Pods/

1.4 权限适配
针对单媒体的用户,允许获取idfa和定位权限的,投放定向广告;不允许获取权限的用户,投放通投广告,媒体可以选择是否把idfa和定位数据提供给优量汇,并承担相应广告填充和eCPM单价下降损失的结果。
idfa的适配请看本文的第三章节
GPS信息获取开关
在已获得GPS权限的前提下,媒体可以选择是否在广告中获取用户的GPS信息,以便获取定向广告。方法如下:
#import "GDTSDKConfig.h"
[GDTSDKConfig enableGPS:YES]; // 获取用户的GPS信息,默认值为NO
II 、 接入代码示例
2.1 在AppDelegate头文件中导入头文件并声明实例
#import "GDTSplashAd.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate,GDTSplashAdDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) GDTSplashAd *splash;
@property (retain, nonatomic) UIView *bottomView;
@end
2.2 初始化并加载广告数据
在AppDelegate的实现文件中初始化并加载广告数据,开屏广告目前支持全屏开屏和半屏开屏广告两种形式,其中半屏开屏广告支持开发者自定义设置开屏底部的界面,用以展示应用Logo等。
- 注册媒体ID
 
    BOOL result = [GDTSDKConfig registerAppId:@"xxx"];//
    
    
    if (result) {
        [self setupGDTSplashAd];
        NSLog(@"注册成功");
    }
- 初始化开屏广告位ID
 
    GDTSplashAd *splash = [[GDTSplashAd alloc] initWithPlacementId:@"6806"];
- 先拉取(LoadAd),再手动调用显示(splash show)
 
  // splash LoadAd 逻辑
  GDTSplashAd *splash = [[GDTSplashAd alloc] initWithPlacementId:YOUR_PLACEMENT_ID];
  splash.delegate = self; //设置代理
  //根据iPhone设备不同设置不同背景图
  if ([[UIScreen mainScreen] bounds].size.height >= 568.0f) {
   splash.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"LaunchImage-568h"]];
  } else {
   splash.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"LaunchImage"]];
  }
  splash.fetchDelay = 3; //开发者可以设置开屏拉取时间,超时则放弃展示
  [splashAd loadFullScreenAd];
拉取成功之后手动调用来显示广告
#pragma mark - GDTSplashAdDelegate
- (void)splashAdDidLoad:(GDTSplashAd *)splashAd {
//    if (splashAd.splashZoomOutView) {
//        [self.view addSubview:splashAd.splashZoomOutView];
//        splashAd.splashZoomOutView.rootViewController = self;
//        // 支持拖拽
//        [splashAd.splashZoomOutView supportDrag];
//    }
    NSLog(@"%s", __func__);
    
    NSString *text = [NSString stringWithFormat:@"%@ 广告拉取成功", splashAd.adNetworkName];
    
    
    NSLog(@"ecpm:%ld ecpmLevel:%@ text:%@", splashAd.eCPM, splashAd.eCPMLevel,text);
    
    // splash show逻辑
    //设置开屏自定义 logo,展示半屏开屏广告
    
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    [self.splash showFullScreenAdInWindow:window withLogoImage:[UIImage imageNamed:@"img_login_logo"] skipView:nil];
}
- (void)splashAdSuccessPresentScreen:(GDTSplashAd *)splashAd
{
    NSLog(@"%s",__FUNCTION__);
//    self.tipsLabel.text = ;
    NSLog(@"广告展示成功");
    
}
设置自定义跳过按钮
UIView *customSkipView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 50)]; // 设置跳过按钮的frame信息
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
[self.splashAd showAdInWindow:window withBottomView:self.bottomView skipView:customSkipView];
通过backgroundImage  根据iPhone设备不同设置不同背景图
需要iPhone 8 Plus@2x.png、iPhoneX@2x.png、iPhone4@1x_2.png,以及启动页的SplashLogo.png
    UIImage *splashImage = [UIImage imageNamed:@"SplashNormal"];
    if (isIPhoneXSeries()) {
        splashImage = [UIImage imageNamed:@"SplashX"];
    } else if ([UIScreen mainScreen].bounds.size.height == 480) {
        splashImage = [UIImage imageNamed:@"SplashSmall"];
    }
    self.splashAd.needZoomOut = self.supportZoomoutViewSwitch.isOn;
    self.splashAd.backgroundImage = splashImage;
    self.splashAd.backgroundImage.accessibilityIdentifier = @"splash_ad";
2.3 开屏广告Demo
demo 中的广告案例
    self.demoArray = [@[
                        @[@"自渲染2.0", @"UnifiedNativeAdViewController"],
                        @[@"开屏广告", @"SplashViewController"],
                        @[@"原生模板广告", @"NativeExpressAdViewController"],
                        @[@"原生视频模板广告", @"NativeExpressVideoAdViewController"],
                        @[@"激励视频广告", @"RewardVideoViewController"],
                        @[@"HybridAd", @"HybridAdViewController"],
                        @[@"Banner2.0", @"UnifiedBannerViewController"],
                        @[@"插屏2.0", @"UnifiedInterstitialViewController"],
                        @[@"插屏2.0全屏", @"UnifiedInterstitialFullScreenVideoViewController"],
                        @[@"获取IDFA", @(1)],
                        @[@"试玩广告调试", @"PlayableAdTestViewController"],
                        ] mutableCopy];
#import "SplashViewController.h"
注册媒体ID API
/**
 SDK 注册接口,请在 app 初始化时调用。
 @param appId - 媒体ID
 
 @return 注册是否成功。
*/
+ (BOOL)registerAppId:(NSString *)appId;
/**
 *  开屏广告的背景图片
 *  可以设置背景图片作为开屏加载时的默认背景
 */
@property (nonatomic, strong) UIImage *backgroundImage;
/**
 *  开屏广告的背景色
 *  可以设置开屏图片来作为开屏加载时的默认图片
 */
@property (nonatomic, copy) UIColor *backgroundColor;
/**
 *  发起拉取全屏广告请求,只拉取不展示
 *  详解:广告素材及广告图片拉取成功后会回调splashAdDidLoad方法,当拉取失败时会回调splashAdFailToPresent方法
 */
- (void)loadFullScreenAd;
/**
 *  展示全屏广告,调用此方法前需调用isAdValid方法判断广告素材是否有效
 *  详解:广告展示成功时会回调splashAdSuccessPresentScreen方法,展示失败时会回调splashAdFailToPresent方法
 */
- (void)showFullScreenAdInWindow:(UIWindow *)window withLogoImage:(UIImage *)logoImage skipView:(UIView *)skipView;
 /**
 *  发起拉取广告请求,只拉取不展示
 *  详解:广告素材及广告图片拉取成功后会回调splashAdDidLoad方法,当拉取失败时会回调splashAdFailToPresent方法
 */
- (void)loadAd;
/**
 *  展示广告,调用此方法前需调用isAdValid方法判断广告素材是否有效
 *  详解:广告展示成功时会回调splashAdSuccessPresentScreen方法,展示失败时会回调splashAdFailToPresent方法
 */
- (void)showAdInWindow:(UIWindow *)window withBottomView:(UIView *)bottomView skipView:(UIView *)skipView;
/**
 * 返回广告是否可展示
 * 对于并行请求,在调用showAdInWindow前时需判断下
 * @return 当广告已经加载完成且未曝光时,为YES,否则为NO
 */
- (BOOL)isAdValid;
III、适配idfa
3.1 适配开屏广告
为iOS14.5升级所需要的修改,目的是请求用户授权,访问与应用相关的数据以跟踪用户或设备。详情请访问developer.apple.com/documentati…
在Info.plist中添加NSUserTrackingUsageDescription,描述获取IDFA等广告标识符的用途
<key>NSUserTrackingUsageDescription</key>
<string>该ID将用于向您推送个性化广告</string>
弹窗小字文案建议:
- 
获取标记权限向您提供更优质、安全的个性化服务及内容,未经同意我们不会用于其他目的;开启后,您也可以前往系统“设置-隐私 ”中随时关闭。
 
- 
获取IDFA等广告标识符权限向您提供更优质、安全的个性化服务及内容;开启后,您也可以前往系统“设置-隐私 ”中随时关闭。
 
展示授权弹窗需要调用requestTrackingAuthorizationWithCompletionHandler:方法。
建议流量主等待方法回调完成后处理广告相关逻辑,这样如果用户授权使用IDFA等广告标识符信息,优量汇iOS SDK可以使用IDFA等广告标识符进行广告请求。代码如下:
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <AdSupport/AdSupport.h>
...
- (void)requestIDFA {
  [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
    //  授权完成回调
     [self loadGDTAd];// 先加载
  }];
}
完整适配代码如下
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    BOOL result = [GDTSDKConfig registerAppId:@""];//
    
    
    if (result) {
//        [self setupGDTSplashAd];
        [self requestIDFA];
        
        NSLog(@"注册成功");
    }
    return YES;
}
- (void)setupGDTSplashAd{
    
    // splash LoadAd 逻辑
    GDTSplashAd *splash = [[GDTSplashAd alloc] initWithPlacementId:@""];
    
    splash.delegate = self; //设置代理
    
//    if ([[UIScreen mainScreen] bounds].size.height >= 568.0f) {
//     splash.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"LaunchImage-568h"]];
//    } else {
//     splash.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"LaunchImage"]];
//    }
    splash.fetchDelay = 5; //开发者可以设置开屏拉取时间,超时则放弃展示
    self.splash =splash;
    
    //根据iPhone设备不同设置不同背景图
    
    UIImage *splashImage = [UIImage imageNamed:@"SplashNormal"];
    if (isIPhoneXSeries()) {
        splashImage = [UIImage imageNamed:@"SplashX"];
    } else if ([UIScreen mainScreen].bounds.size.height == 480) {
        splashImage = [UIImage imageNamed:@"SplashSmall"];
    }
    // * 是否需要开屏视频V+功能
//    self.splash.needZoomOut = self.supportZoomoutViewSwitch.isOn;
    self.splash.backgroundImage = splashImage;
    self.splash.backgroundImage.accessibilityIdentifier = @"splash_ad";
    //
    
    [self.splash loadFullScreenAd];
    
}
#pragma mark - GDTSplashAdDelegate
- (void)splashAdDidLoad:(GDTSplashAd *)splashAd {
//    if (splashAd.splashZoomOutView) {
//        [self.view addSubview:splashAd.splashZoomOutView];
//        splashAd.splashZoomOutView.rootViewController = self;
//        // 支持拖拽
//        [splashAd.splashZoomOutView supportDrag];
//    }
    NSLog(@"%s", __func__);
    
    NSString *text = [NSString stringWithFormat:@"%@ 广告拉取成功", splashAd.adNetworkName];
    
    
    NSLog(@"ecpm:%ld ecpmLevel:%@ text:%@", splashAd.eCPM, splashAd.eCPMLevel,text);
    
    // splash show逻辑
    //设置开屏自定义 logo,展示半屏开屏广告
    
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    [self.splash showFullScreenAdInWindow:window withLogoImage:[UIImage imageNamed:@"img_login_logo"] skipView:nil];
}
- (void)splashAdSuccessPresentScreen:(GDTSplashAd *)splashAd
{
    NSLog(@"%s",__FUNCTION__);
//    self.tipsLabel.text = ;
    NSLog(@"广告展示成功");
    
}
- (void)requestIDFA {
    
    
    if([self isNeedrequestTrackingAuthorization]){
        
        if (@available(iOS 14, *)) {
            [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
              //  授权完成回调
               [self setupGDTSplashAd ];// 先加载loadGDTAd
            }];
              
        } else {
            [self setupGDTSplashAd ];// 先加载loadGDTAd
        }
    }else{
        
        [self setupGDTSplashAd ];// 先加载loadGDTAd
    }
    
    
    
    
}
- (BOOL)isNeedrequestTrackingAuthorization{
    
if (@available(iOS 14, *)) {
        ATTrackingManagerAuthorizationStatus status = ATTrackingManager.trackingAuthorizationStatus;
        switch (status) {
            case ATTrackingManagerAuthorizationStatusDenied:
                NSLog(@"用户拒绝");
                return YES;
                
                break;
            case ATTrackingManagerAuthorizationStatusAuthorized:
                NSLog(@"用户允许");
                break;
            case ATTrackingManagerAuthorizationStatusNotDetermined:
                NSLog(@"用户为做选择或未弹窗");
                return YES;
                break;
            default:
                break;
        }
    } else {
        // Fallback on earlier versions
       if ([ASIdentifierManager.sharedManager isAdvertisingTrackingEnabled]) {
       }else {
          NSLog(@"用户开启了限制广告追踪");
       }
    }
    
    
    return NO;
    
    
}
对于用户拒绝授权 UserTracking 的情况,可以考虑接入苹果的 SKAdNetwork 框架进行广告分析。
developer.apple.com/documentati…
    <key>SKAdNetworkItems</key>
    <array>
        <dict>
            <key>SKAdNetworkIdentifier</key>
            <string>f7s53z58qe.skadnetwork</string>
        </dict>
        <dict>
             <key>SKAdNetworkIdentifier</key>
             <string>example200.skadnetwork</string>
        </dict>
    </array>
SKAdNetworkIdentifier : f7s53z58qe.skadnetwork


3.2 适配极光推送
iOS14.5.1适配【To use the AppTrackingTransparency framework】使用AppTrackingTransparency以请求用户授权获取IDFA信息
————————————————
版权声明:本文为CSDN博主「#公众号:iOS逆向」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:blog.csdn.net/z929118967/…
注册极光
    [JPUSHService setupWithOption:launchOptions appKey:@"" channel:@"App Store" apsForProduction:YES advertisingIdentifier:[self testIDFA]];
获取idfaString
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <AdSupport/AdSupport.h>
- (NSString*)testIDFA {
    NSString  __block *idfaString = @"";
    
    
    
    if (@available(iOS 14, *)) {
        [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) {
            if (status == ATTrackingManagerAuthorizationStatusAuthorized) {
                idfaString  = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
                
            }
        }];
    } else {
        // 使用原方式访问 IDFA
        if ([[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled]) {
             idfaString = [[ASIdentifierManager sharedManager] advertisingIdentifier].UUIDString;
        }
    }
    NSLog(@"idfaString: %@", idfaString);
    return idfaString;
    
}
3.3 上架权限配置
新增开屏广告功能,appstoreconnect后台的app隐私声明,采集数据需要包含idfa,需要添加idfa用于广告,否则会被拒绝。
具体配置:
app主页的隐私收集这次新增了标识符->广告标识符用于第三方广告,未与用户身份关联,会将设备ID用于追踪目的。

IV 常见问题
4.1 SDK 从4.12.90升级4.13.26之后的适配
-> Installing GDTMobSDK 4.13.26 (was 4.12.90)
2021-11-01 09:55:54.785169+0800 +[GDTSDKConfig enableGPS:]: unrecognized selector sent to class 0x104cf83b8
解决方法:直接注释
//    [GDTSDKConfig enableGPS:YES]; // 获取用户的GPS信息,默认值为NO
4.2 iOS13适配present半屏的问题
由于本文的广告类型是开屏全屏方式的,所以使用分类将广告控制器GDTSplashImageViewController的modalPresentationStyle设置为UIModalPresentationFullScreen
点击广告之后,如果不是打开第三方app,则会modal到GDTLandingPageWebViewController进行网页的展示,所以最好将GDT开头的控制器都是以UIModalPresentationFullScreen的方式展示。
    if([NSStringFromClass(viewControllerToPresent.class) hasPrefix:@"GDT"])
    {
        
        
        
        
        return UIModalPresentationFullScreen;
        
        
    }
使用分类控制modal的样式
- (void)K_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion {
    if (@available(iOS 13.0, *)) {
        if (viewControllerToPresent.K_automaticallySetModalPresentationStyle) {
            
            
            viewControllerToPresent.modalPresentationStyle = [QCTSession getModalPresentationStyleWith:viewControllerToPresent];
            
            
            
                        
            
        }
        [self K_presentViewController:viewControllerToPresent animated:flag completion:completion];
    } else {
        // Fallback on earlier versions
        [self K_presentViewController:viewControllerToPresent animated:flag completion:completion];
    }
}
调试发现广告页对应的控制器是 GDTSplashImageViewController
(lldb) po [QCT_Common getCurrentVC]
<GDTSplashImageViewController: 0x159ea5f70>
superclass:GDTSplashViewController
title:(null)
view:<UIView: 0x159d30d80; frame = (0 0; 414 736); autoresize = W+H; layer = <CAGradientLayer: 0x280f56640>>
更多适配细节,请看这篇文章:
blog.csdn.net/z929118967/…
- (NSMutableArray *)FullScreenClasss{
    
    if(_FullScreenClasss == nil){
        
        _FullScreenClasss = [NSMutableArray array];
        
        
        [_FullScreenClasss addObject:@"PGDatePickManager"];
        
        [_FullScreenClasss addObject:@"GDTSplashImageViewController"];
    }
    return _FullScreenClasss;
    
}
getCurrentVC的实现
// 获取当前VC
+ (UIViewController *)getCurrentVC
{
    UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
    
    UIViewController *currentVC = [self getCurrentVCFrom:rootViewController];
    
    return currentVC;
}
+ (UIViewController *)getCurrentVCFrom:(UIViewController *)rootVC
{
    UIViewController *currentVC;
    
    if ([rootVC presentedViewController]) {
        // 视图是被presented出来的
        rootVC = [rootVC presentedViewController];
    }
    
    if ([rootVC isKindOfClass:[UITabBarController class]]) {
        // 根视图为UITabBarController
        currentVC = [self getCurrentVCFrom:[(UITabBarController *)rootVC selectedViewController]];
        
    } else if ([rootVC isKindOfClass:[UINavigationController class]]){
        // 根视图为UINavigationController
        currentVC = [self getCurrentVCFrom:[(UINavigationController *)rootVC visibleViewController]];
    } else {
        // 根视图为非导航类
        currentVC = rootVC;
    }
    return currentVC;
}
see also
更多内容请关注#小程序:iOS逆向,只为你呈现有价值的信息,专注于移动端技术研究领域。
作者:公众号iOS逆向