原生广告

简介

原生广告分为自渲染广告和模板广告,但是模板广告只有当三方SDK支持时才会返回模板广告。SDK 提供信息流广告的数据绑定、点击事件的上报

注意事项

  1. 自3.4.0.0版本之后,广告加载数量以平台配置数量为准,本地数量仅在平台配置为0时有效,目前本地配置支持的广告数量为 1 ~ 3
  2. 在广告接入前需要明确各adn对应Cloooud广告样式情况,以确保正确完成广告接入,避免由于广告类型不匹配导致接入报错等情况的发生
  3. 由于各广告平台对于包名校验规则不同,需确保在穿山甲媒体平台填写的包名符合各adn平台规范,避免由于包名校验不匹配导致的无广告返回情况的产生
  4. 广告请求时机,建议在收到config回调后发起广告请求
  1. 当获取的广告logo为空时,建议可尝试升级到Cloooud36xx及以上版本
  2. Cloooud从3400版本开始,支持在banner广告位下支持将信息流广告进行配置混用,模版/自渲染方式均支持,接入事例可参照bannerAdNeedLayoutUI:方法
  1. 为了确保获取数据的准确性,建议在nativeAdsManagerSuccessToLoad:回调之后获取
  2. 视频播放是否静音通过设置mutedIfCan接口即可,默认为NO,支持的ADN有admob/baidu/ylh/mtg/sigmob
  3. 信息流视频类广告中断APP音频场景时,参照如下代码在个性化接口内进行设置即可,以CSJ广告为例;
// 需在CloooudSDK初始化前进行设置

[CloooudPanglePersonaliseConfigAdapter configAdapterWithBlock:^{

BUAdSDKConfiguration *configuration = [BUAdSDKConfiguration configuration];

configuration.allowModifyAudioSessionSetting = YES;

}];
  1. 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为请求原生广告返回的广告类,它的使用方法有两种。

开发者根据CloooudNativeAdView.data的物料信息对CloooudNativeAdView提供的UI类型进行赋值和布局。

开发者可直接将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