原生广告
简介
原生广告分为自渲染广告和模板广告,但是模板广告只有当三方SDK支持时才会返回模板广告。SDK 提供信息流广告的数据绑定、点击事件的上报
- 自渲染广告:聚合SDK返回物料,由开发者在返回的CloooudNativeAdView类型的view上进行子视图的自行渲染和展示。
- 模板广告:聚合SDK直接返回渲染好的广告view:CloooudNativeAdView,开发者直接展示即可。
注意事项
- 自3.4.0.0版本之后,广告加载数量以平台配置数量为准,本地数量仅在平台配置为0时有效,目前本地配置支持的广告数量为 1 ~ 3
- 在广告接入前需要明确各adn对应Cloooud广告样式情况,以确保正确完成广告接入,避免由于广告类型不匹配导致接入报错等情况的发生
- 由于各广告平台对于包名校验规则不同,需确保在穿山甲媒体平台填写的包名符合各adn平台规范,避免由于包名校验不匹配导致的无广告返回情况的产生
- 广告请求时机,建议在收到config回调后发起广告请求
- 当config回调一直失败时,建议首先明确appid及广告位id是否赋值正确,是否有多余空格,是否是网络不文档导致的超时等,当排查后无法定位问题时,建议通过抓包将config字段下的加密内容提供过来,我们协助定位
- 当获取的广告logo为空时,建议可尝试升级到Cloooud36xx及以上版本
- Cloooud从3400版本开始,支持在banner广告位下支持将信息流广告进行配置混用,模版/自渲染方式均支持,接入事例可参照bannerAdNeedLayoutUI:方法
- 需注意当版本不匹配时下,信息流广告无填充,当发生此场景时,建议通过流量分组来区分Cloooud SDK版本来进行区分
- 为了确保获取数据的准确性,建议在nativeAdsManagerSuccessToLoad:回调之后获取
- 视频播放是否静音通过设置mutedIfCan接口即可,默认为NO,支持的ADN有admob/baidu/ylh/mtg/sigmob
- 信息流视频类广告中断APP音频场景时,参照如下代码在个性化接口内进行设置即可,以CSJ广告为例;
// 需在CloooudSDK初始化前进行设置
[CloooudPanglePersonaliseConfigAdapter configAdapterWithBlock:^{
BUAdSDKConfiguration *configuration = [BUAdSDKConfiguration configuration];
configuration.allowModifyAudioSessionSetting = YES;
}];
- 2.7.0.0版本及以上已支持信息流模版渲染和自渲染混出,即同一广告位下支持不同代码位使用不同的渲染方式。媒体可以根据实际需要在平台完成配置。在平台配置的广告位类型优先级高于iOS端上配置的getExpressAdIfCan属性。当平台配置该设置后,开发者不需要设置getExpressAdIfCan属性; 反之,开发者需要设置getExpressAdIfCan属性来告知SDK当前广告位下是否为模板类型。 a. 原生广告返回是否为模板广告最终取决于CloooudNativeAdView对象填充后的hasExpressAdGot属性 YES:模板广告 NO:自渲染广告 b. 原生模板广告的大小配置:CloooudAdUnit的adSize属性 当对应代码位是模板广告时,实际adSize的宽是传入的宽度,高是各adn按实际比例根据传入宽度自动适配的一个高度值。 eg:// 如果是模板广告,返回高度将不一定是300,而是按照414和对应代码位在平台的配置计算出的高度(只有自渲染类原生广告的ADN有:Admob,Mintegral,Admob)
slot1.adSize = CGSizeMake(414, 300);
原生广告管理类CloooudNativeAdsManager
在SDK里只需要使用 CloooudNativeAdsManager 就可以获取信息流广告。CloooudNativeAdsManager支持多广告加载,可以一次加载返回多个广告。
注意:一定要设置rootViewController,即跳转落地页需要的viewController。
// Native广告加载类
@interface CloooudNativeAdsManager : CloooudBaseAd
// 广告构建方法,开发者可使用'CloooudAdUnit'类创建协议对象,也可自行创建类实现协议
// @param slot 广告位ID
- (instancetype)initWithSlot:(id<CloooudNativeAdSlot>)slot;
// 快速广告构建方法
// @param unitID 广告位ID
// @param adSize 广告视图大小
- (instancetype)initWithAdUnitID:(NSString *)unitID adSize:(CGSize)adSize;
// 是否使用模板广告,只对支持模板广告的第三方SDK有效,默认为NO,仅在广告加载前设置有效,优先以平台配置为准
@property (nonatomic, assign) BOOL getExpressAdIfCan;
// 图片大小,包括视频媒体的大小设定
@property (nonatomic, assign) CGSize imageOrVideoSize;
// 广告视图的尺寸,请勿设置为0
@property (nonatomic, assign) CGSize adSize;
// 是否静音播放视频,是否真实静音由adapter确定,默认为NO,仅在广告加载前设置有效,优先以平台配置为准
@property (nonatomic, assign) BOOL startMutedIfCan;
// 广告代理对象
@property (nonatomic, weak) id<CloooudNativeAdsManagerDelegate> delegate;
// 必要,设置广告详情跳转控制器
@property (nonatomic, weak) UIViewController *rootViewController;
// 加载广告方法
// @param count 加载广告的数量,建议单次不超过3个,优先以平台上配置为准
- (void)loadAdDataWithCount:(NSUInteger)count;
- (void)loadAdData;
// 不再使用加载成功后回调的视图对象组时,可调用该方法释放占用的内存
- (void)destory;
@end
原生广告类CloooudNativeAdView
CloooudNativeAdView为请求原生广告返回的广告类,它的使用方法有两种。
- 自渲染广告(hasExpressAdGot=NO)
开发者根据CloooudNativeAdView.data的物料信息对CloooudNativeAdView提供的UI类型进行赋值和布局。
- 模板广告(hasExpressAdGot=YES)
开发者可直接将CloooudNativeAdView添加到父视图上进行展示
注意:目前仅支持frame布局方式,不支持自动布局
@interface CloooudNativeAdView : CloooudCanvasView
// 广告管理者
@property (nonatomic, weak, readonly) CloooudNativeAdsManager *adManager;
// 代理协议对象
@property (nonatomic, weak, readwrite, nullable) id<CloooudNativeAdViewDelegate> delegate;
// 视频播放代理协议对象
@property (nonatomic, weak, readwrite, nullable) id<CloooudNativeAdVideoDelegate> videoDelegate;
// 是否是模板广告,由adapter开发者实现
@property (nonatomic, assign, readonly) BOOL isExpressAd;
// 是否是模板广告,由adapter开发者实现,同isExpressAd
@property (nonatomic, assign, readonly) BOOL hasExpressAdGot;
// [必传]跳转控制器
@property (nonatomic, weak, readwrite) UIViewController *_Nullable rootViewController;
// 是否已经准备广告展示,理论上在广告加载回调后即为YES,但受一些因素的影响(例如广告失效),可能为NO。建议在广告展示前调用该方法进行是否可以展示
@property (nonatomic, assign, readonly) BOOL isReady;
// 广告视图的唯一标识
@property (nonatomic, copy, readonly) NSString *adViewID;
@end
自渲染广告物料信息
当返回广告是自渲染类型时,开发者需要使用CloooudNativeAdView.data的数据进行自渲染布局.该类型数据结构如下
typedef NSObject<CloooudMediatedNativeAdData> CloooudMaterialMeta;
// 媒体native ad数据协议,配合CloooudMediatedNativeAdViewCreator使用
@protocol CloooudMediatedNativeAdData <NSObject>
// 广告支持的跳转类型
@property (nonatomic, assign, readonly) CloooudMediatedNativeAdCallToType callToType;
// 物料图片集,如果图片有宽高,请尽量配置width和height
@property (nonatomic, copy, readonly, nullable) NSArray<CloooudImage *> *imageList;
// app类型广告的广告商app图标,如果图标有宽高,请尽量配置width和height
@property (nonatomic, strong, readonly, nullable) CloooudImage *icon;
// 广告adn的logo,如果logo有宽高,请尽量配置width和height
@property (nonatomic, strong, readonly, nullable) CloooudImage *adLogo;
// 广告标题
@property (nonatomic, copy, readonly, nullable) NSString *adTitle;
// 广告详情描述
@property (nonatomic, copy, readonly, nullable) NSString *adDescription;
// 应用来源、市场,例如'App Store'
@property (nonatomic, copy, readonly, nullable) NSString *source;
// 按钮文案,例如'下载/安装'
@property (nonatomic, copy, readonly, nullable) NSString *buttonText;
// 图片/视频模式
@property (nonatomic, assign, readonly) CloooudMediatedNativeAdMode imageMode;
// app评分,区间为1-5,如果没有值返回-1
@property (nonatomic, assign, readonly) NSInteger score;
// 评论数量,如果没有值返回-1
@property (nonatomic, assign, readonly) NSInteger commentNum;
// 广告安装包体大小,单位KB,如果没有值返回-1
@property (nonatomic, assign, readonly) NSInteger appSize;
// 视频时长,单位秒,如果没有值返回0
@property (nonatomic, assign, readonly) NSInteger videoDuration;
// 视频纵横比(width/height),如果没有值或者异常返回0
@property (nonatomic, assign, readonly) CGFloat videoAspectRatio;
// 媒体扩展数据
@property (nonatomic, copy, readonly, nullable) NSDictionary *mediaExt;
// app购买价格,例如'免费',没有则为nil
@property (nonatomic, strong, readonly, nullable) NSString *appPrice;
// 广告商标识,广告商的名称或者链接
@property (nonatomic, copy, readonly, nullable) NSString *advertiser;
// 品牌名称,若广告返回中无品牌名称则为空
@property (copy, nonatomic, readonly, nullable) NSString *brandName;
// ADN提供的不喜欢广告的原因,可能为空
@property (nonatomic, copy, readonly, nullable) NSArray<CloooudDislikeReason *> *dislikeReasons;
// ADN提供的视频类型广告的资源路径,部分ADN需要申请白名单,可能为空
@property (nonatomic, copy, readonly, nullable) NSString *videoUrl;
@end
回调监听
CloooudNativeAdsManagerDelegate
@protocol CloooudNativeAdsManagerDelegate <NSObject>
@optional
/// Native 广告加载成功回调
/// @param adsManager 广告管理对象
/// @param nativeAdViewArray 广告视图,Cloooud包装视图对象组,包括模板广告和自渲染广告
- (void)nativeAdsManagerSuccessToLoad:(CloooudNativeAdsManager *_Nonnull)adsManager nativeAds:(NSArray<CloooudNativeAdView *> *_Nullable)nativeAdViewArray;
/// Native 广告加载失败回调
/// @param adsManager 广告管理对象
/// @param error 加载出错信息
- (void)nativeAdsManager:(CloooudNativeAdsManager *_Nonnull)adsManager didFailWithError:(NSError *_Nullable)error;
@end
CloooudNativeAdViewDelegate
@protocol CloooudNativeAdViewDelegate <NSObject>
@optional
// 模板广告渲染成功回调,非模板广告不会回调,模板广告可能不会回调
// @param nativeExpressAdView 模板广告对象
- (void)nativeAdExpressViewRenderSuccess:(CloooudNativeAdView *_Nonnull)nativeExpressAdView;
// 模板广告渲染成功回调,非模板广告不会回调,模板广告可能不会回调
// @param nativeExpressAdView 模板广告对象
// @param error 渲染出错原因
- (void)nativeAdExpressViewRenderFail:(CloooudNativeAdView *_Nonnull)nativeExpressAdView error:(NSError *_Nullable)error;
// 广告展示回调,不区分模板与非模板
// @param nativeAdView 广告对象
- (void)nativeAdDidBecomeVisible:(CloooudNativeAdView *_Nonnull)nativeAdView;
// 广告视频播放状态变更回调,是否回调与adapter实现有关
// @param nativeAdView 广告对象
// @param playerState 播放状态
- (void)nativeAdExpressView:(CloooudNativeAdView *_Nonnull)nativeAdView stateDidChanged:(CloooudPlayerPlayState)playerState;
// 广告点击事件回调
// @param nativeAdView 广告对象
// @param view 广告展示视图
- (void)nativeAdDidClick:(CloooudNativeAdView *_Nonnull)nativeAdView withView:(UIView *_Nullable)view;
// 广告即将展示全屏页面/商店时触发
// @param nativeAdView 广告视图
- (void)nativeAdViewWillPresentFullScreenModal:(CloooudNativeAdView *_Nonnull)nativeAdView;
// 广告即将退出全屏页面/商店时触发
// @param nativeAdView 广告视图
- (void)nativeAdViewDidDismissFullScreenModal:(CloooudNativeAdView *_Nonnull)nativeAdView;
// 模板广告点击关闭时触发
// @param nativeAdView 广告视图
// @param filterWords 广告关闭原因,adapter开发者透传数据
- (void)nativeAdExpressViewDidClosed:(CloooudNativeAdView *_Nullable)nativeAdView closeReason:(NSArray<NSDictionary *> *_Nullable)filterWords;
@end
CloooudNativeAdVideoDelegate
当返回广告为视频广告,开发者注册该监听后可以收到相应的回调。当且仅当CloooudNativeAdView.data.imageMode = CloooudFeedVideoAdModeImage生效。
@protocol CloooudNativeAdVideoDelegate <NSObject>
@optional
/// 当视频播放状态改变之后触发
/// @param nativeAdView 广告视图
/// @param playerState 变更后的播放状态
- (void)nativeAdVideo:(CloooudNativeAdView *_Nullable)nativeAdView stateDidChanged:(CloooudPlayerPlayState)playerState;
/// 广告视图中视频视图被点击时触发
/// @param nativeAdView 广告视图
- (void)nativeAdVideoDidClick:(CloooudNativeAdView *_Nullable)nativeAdView;
/// 广告视图中视频播放完成时触发
/// @param nativeAdView 广告视图
- (void)nativeAdVideoDidPlayFinish:(CloooudNativeAdView *_Nullable)nativeAdView;
@end
自渲染广告示例
广告加载和自渲染物料的获取
// 广告加载
- (void)loadNativeAds {
self.adManager = [[CloooudNativeAdsManager alloc] initWithAdUnitID:self.viewModel.adUnitID adSize:CGSizeMake(414, 300)];
self.adManager.imageOrVideoSize = CGSizeMake(1080, 1920);
self.adManager.rootViewController = self;
self.adManager.startMutedIfCan = NO;
// 如果需要场景,请设置该属性
self.adManager.scenarioID = @"xxx";
self.adManager.delegate = self;
//该逻辑用于判断配置是否拉取成功。如果拉取成功,可直接加载广告,否则需要调用setConfigSuccessCallback,传入block并在block中调用加载广告。SDK内部会在配置拉取成功后调用传入的block
__weak typeof(self) weakself = self;
if([CloooudAdSDKManager configDidLoad]){
[self.adManager loadAdDataWithCount:3];
}else{
[CloooudAdSDKManager addConfigLoadSuccessObserver:self withAction:^(id _Nonnull observer) {
CloooudD_Log(@"%s: ----setConfigSuccessCallback", __func__);
[weakself.adManager loadAdDataWithCount:3];
}];
}
配套Demo使用了一个tableView格式的信息流来展示原生广告。主要使用refreshUIWithModel方法来进行具体cell的渲染布局:
- (void)refreshUIWithModel:(CloooudNativeAdView *)model {
[super refreshUIWithModel:model];
self.nativeAdView.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), CGRectGetHeight(self.frame));
CGFloat width = CGRectGetWidth(self.contentView.bounds);
CGFloat contentWidth = (width - 2 * margin);
CGFloat y = padding.top;
NSAttributedString *attributedText = [CloooudDFeedStyleHelper titleAttributeText:model.data.AdTitle];
CGSize titleSize = [attributedText boundingRectWithSize:CGSizeMake(contentWidth, 0) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:0].size;
self.nativeAdView.titleLabel.frame = CGRectMake(padding.left, y, contentWidth, titleSize.height);
self.nativeAdView.titleLabel.attributedText = attributedText;
// 广告标识
if (model.data.adLogo.imageURL) {
UIImageView *adLogoV = [[UIImageView alloc] initWithFrame:CGRectMake(width-20, y, 20, 20)];
[adLogoV setImageWithURL:model.data.adLogo.imageURL];
[self.nativeAdView addSubview:adLogoV];
}
y += titleSize.height;
y += 5;
CloooudImage *image = model.data.imageAry.firstObject;
const CGFloat imageHeight = contentWidth * (image.height / image.width);
self.nativeAdView.imageView.frame = CGRectMake(padding.left, y, contentWidth, imageHeight);
[self.nativeAdView.imageView setImageWithURL:image.imageURL placeholderImage:nil];
self.nativeAdView.iconImageView.frame = CGRectMake(contentWidth - logoSize.width, imageHeight - logoSize.height, logoSize.width, logoSize.height);
y += imageHeight;
y += 10;
CGFloat originInfoX = padding.left;
CGFloat dislikeX = width - 24 - padding.right;
// 物料信息不包含关闭按钮需要自己实现
if (!self.nativeAdView.dislikeBtn) {
self.nativeAdView.dislikeBtn = [[UIButton alloc] init];
[self.nativeAdView.dislikeBtn setImage:[UIImage imageNamed:@"feedClose"] forState:UIControlStateNormal];
}
self.nativeAdView.dislikeBtn.frame = CGRectMake(dislikeX, y, 24, 20);
// sdk标识
UIImageView *sdkLogoV;
if (model.data.adLogo) {
sdkLogoV = [[UIImageView alloc] initWithFrame:CGRectMake(padding.left, y, 0, 0)];
// 优先使用image
if (model.data.adLogo.image) {
[sdkLogoV setImage:model.data.adLogo.image];
} else if (model.data.adLogo.imageURL) {
// image不存在使用url
[sdkLogoV setImageWithURL:model.data.adLogo.imageURL];
}
if (sdkLogoV.image) {
CGFloat width = 20;
CGFloat height = 20;
if (model.data.adLogo.width && model.data.adLogo.height) {
height = width * model.data.adLogo.height/model.data.adLogo.width;
}
sdkLogoV.frame = CGRectMake(CGRectGetMinX(sdkLogoV.frame), CGRectGetMinY(sdkLogoV.frame),width, height);
[self.nativeAdView addSubview:sdkLogoV];
}
}
CGFloat maxInfoWidth = width - 2 * margin - 24 - 24 - 10 - 100;
self.nativeAdView.descLabel.frame = CGRectMake(originInfoX+30, CGRectGetMinY(sdkLogoV.frame), maxInfoWidth, 20);
self.nativeAdView.descLabel.attributedText = [CloooudDFeedStyleHelper subtitleAttributeText:model.data.AdDescription];
if (self.nativeAdView.hasSupportActionBtn) {
CGFloat customBtnWidth = 100;
self.nativeAdView.callToActionBtn.frame = CGRectMake(dislikeX - customBtnWidth - 5, y, customBtnWidth, 20);
NSString *btnTxt = @"Click";
if (self.nativeAdView.data.buttonText.length > 0) {
btnTxt = self.nativeAdView.data.buttonText;
}
[self.nativeAdView.callToActionBtn setTitle:btnTxt forState:UIControlStateNormal];
self.nativeAdView.callToActionBtn.backgroundColor = [UIColor redColor];
}
//物料里的price,score,source等信息;开发者可根据原始信息自定义表现形式
CGRect frame = CGRectMake(padding.left, CGRectGetMaxY(self.nativeAdView.descLabel.frame)-5, 260, 20);
UILabel *otherInfoLbl = [self otherInfoLblWithFrame:frame data:self.nativeAdView.data];
[self.nativeAdView addSubview:otherInfoLbl];
// 注册点击事件
[self.nativeAdView registerClickableViews:@[
self.nativeAdView.titleLabel,
self.nativeAdView.descLabel,
self.nativeAdView.imageView,
self.nativeAdView.callToActionBtn
]];
[self.contentView addSubview:self.nativeAdView];
}
Demo针对大图,组图和视频类型进行了分别的渲染示例,需要注意的是视频类型的渲染需要使用mediaView:
self.nativeAdView.mediaView.frame = CGRectMake(padding.left, y, contentWidth, imageHeight);
// !!!mediaView布局后必须调用该方法
[self.nativeAdView reSizeMediaView];
详细示例请参见Demo.
模板示例
模板示例的加载与自渲染广告相同,具体加载类型以平台配置为准
模板的展示的话,返回的View可直接添加到父View上展示。详见Demo