本页目录

广告加载与展示

接入概览 · 运行示例 · 隐私与网络 · 错误处理

本文面向已完成安装的宿主,接口以所安装版本的 framework 公开头为准。先完成宿主隐私流程,再按广告形式创建、强持有对象并设置 delegate。

展示窗口与 Scene

展示、落地页、外跳回流、曝光判断和 UI 生命周期归属广告的实际来源 window/Scene。没有来源时,仅在前台应用 Scene 唯一且明确时兜底,不跨 Scene 随机选择;独立落地页始终属于来源 Scene。

开屏的 rootVC 必须已经入窗。在 Scene 宿主中使用 customWindow 时,窗口必须可见、尺寸有限且为正、已经关联 Scene,并与 rootVC.window 属于同一 Scene;它可以不是 key window,可以使用较高 windowLevel,也可以不设置 rootVC。输入无效时展示失败,修正窗口后可重试。

请求与通用生命周期

SDK 无需额外 App ID 初始化。完成宿主隐私流程后,新建具体广告对象、设置 delegate 并强持有它,再调用 ysifly_loadAd 或 ysifly_loadAdWithRequestConfig:。广告位由平台分配,应与当前 App 和广告形式匹配。

常用请求配置可以封装为函数;各广告对象均可使用返回值:

static YSIFLYAdRequestConfig *MakeAdRequestConfig(void) {
    YSIFLYAdRequestConfig *config = [[YSIFLYAdRequestConfig alloc] init];
    config.requestTimeout = @5;
    config.appName = NSBundle.mainBundle.infoDictionary[@"CFBundleDisplayName"];
    config.appVersion = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"];
    return config;
}

settleType(0 固定价格、1 RTB)、bidFloor(CNY 元/千次展示)、interactStatus(1 开启、2 关闭)按平台接入约定设置。idfa 仅传授权后的有效值;deepLinkDisabled 可控制是否尝试 DeepLink。请求参数通过 YSIFLYAdRequestConfig 设置;展示尺寸、静音和倒计时使用各格式的展示配置。

事件/动作接入规则
DidLoad响应解析成功,可读 bidInfo;内置渲染格式尚须等待素材就绪
DidReady开屏、Banner、插屏主素材就绪,可以结合宿主状态判断是否展示
NativeFeed DidLoad可读 adData;媒体完成 UI 后 attach,没有 Ready 回调
ysifly_isAdValid展示或新挂载前检查有效性;不能替代 Ready、布局或宿主可见性判断
ysifly_destroy取消/结束当前对象,销毁后不能重新加载;NativeFeed 临时离屏使用 detach

可左右滚动查看完整表格

公开 delegate 回调在主线程触发。UI 和挂载操作放在主线程;异步回调须先判断回调对象仍是当前广告。开屏、Banner、插屏成功展示后需要新建实例获取下一次广告机会。媒体永久结束广告流程时置空 delegate、清理容器并释放对象;不要因为广告打开了落地页就提前销毁仍在使用的对象。

开屏广告

宿主还需为启动页设置自己的等待期限;请求超时不等于整个启动流程的等待期限。若 Ready 到达时页面不可见,示例在 viewDidAppear: 再检查;App 重新激活时也应在来源页面调用 tryShowSplash。再次检查前,宿主须确认来源 Scene 在前台、页面仍允许展示且没有冲突的模态界面。若已经进入主页面或超过宿主等待期限,调用 clearSplash 并继续业务,忽略旧实例回调;不要一直停留在启动页。

didFailWithError:、跳过、倒计时关闭以及主动放弃等待都应汇入宿主的“继续主流程”出口。以下代码展示对象管理与展示调用;将 clearSplash 后的业务导航接到自己的入口,保证一次广告机会只推进一次业务。视频完播和点击跳转不等于广告关闭。

以下类可放入 .m 文件,替换广告位后在宿主适当时机调用 loadSplash。完整的手动加载、品牌区和日志页面见 开屏示例。

#import <YSIFLYADLib/YSIFLYADLib.h>

@interface SplashExampleViewController : UIViewController <YSIFLYSplashAdDelegate>
@property (nonatomic, strong) YSIFLYSplashAd *splashAd;
@property (nonatomic, assign) BOOL splashReady;
@end

@implementation SplashExampleViewController

// 在宿主隐私流程完成、页面可展示时调用。
- (void)loadSplash {
    [self clearSplash];
    self.splashAd = [[YSIFLYSplashAd alloc] initWithAdUnitId:@"YOUR_SPLASH_AD_UNIT_ID"];
    self.splashAd.delegate = self;
    self.splashAd.currentViewController = self;
    YSIFLYAdRequestConfig *request = [[YSIFLYAdRequestConfig alloc] init];
    request.requestTimeout = @5;
    [self.splashAd ysifly_loadAdWithRequestConfig:request];
}

- (void)ysifly_splashAdDidReady:(YSIFLYSplashAd *)ad {
    if (ad != self.splashAd) return;
    self.splashReady = YES;
    [self tryShowSplash];
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    [self tryShowSplash]; // Ready 先到、页面后出现时再次检查。
}

- (void)tryShowSplash {
    YSIFLYSplashAd *ad = self.splashAd;
    if (!ad || !self.splashReady || ![ad ysifly_isAdValid] || !self.view.window ||
        self.presentedViewController ||
        UIApplication.sharedApplication.applicationState != UIApplicationStateActive) return;
    if (@available(iOS 13.0, *)) {
        if (self.view.window.windowScene.activationState != UISceneActivationStateForegroundActive) return;
    }
    self.splashReady = NO; // 每次广告机会只发起一次展示。
    YSIFLYSplashAdConfig *config = [[YSIFLYSplashAdConfig alloc] init];
    config.traceDuration = 5;
    config.muteOnStart = YES;
    config.muteButtonHidden = NO;
    [ad ysifly_showAdFromRootViewController:self config:config];
}

- (void)ysifly_splashAd:(YSIFLYSplashAd *)ad didFailWithError:(YSIFLYAdError *)error {
    if (ad != self.splashAd) return;
    NSLog(@"Splash failed: %d %@", error.errorCode, error.errorDescription);
    [self clearSplash]; // 宿主在此继续主流程,避免失败后停在启动页。
}

- (void)ysifly_splashAdDidClose:(YSIFLYSplashAd *)ad {
    if (ad == self.splashAd) [self clearSplash];
}

- (void)ysifly_splashAdDidSkip:(YSIFLYSplashAd *)ad {
    if (ad == self.splashAd) [self clearSplash];
}

- (void)clearSplash {
    self.splashReady = NO;
    self.splashAd.delegate = nil;
    [self.splashAd ysifly_destroy];
    self.splashAd = nil;
}

- (void)dealloc {
    [self.splashAd ysifly_destroy];
}
@end

展示配置与回调

配置/回调含义与接入动作
traceDuration倒计时秒数,有效范围 3~5;默认 5,越界回退为 5
mediumBottomView宿主品牌区,需由媒体设置视图高度;示例提供 Logo 区域
customWindow可选展示窗口;默认使用传入控制器所属 window
muteOnStart / muteButtonHidden视频初始静音状态、静音按钮是否隐藏
ysifly_splashAdDidLoad:响应解析成功,可读 bidInfo 和 hasVideoTemplate,尚不能据此展示
ysifly_splashAdDidReady:主素材已就绪;检查广告有效性与宿主可展示状态
ysifly_splashAdDidShow: / ysifly_splashAdDidExpose:已显示到 window / 已达到有效曝光,是两个事件
ysifly_splashAdDidSkip:用户点击跳过,广告关闭
ysifly_splashAdDidClose:倒计时结束并关闭;不要把视频完播或跳转等同于本回调
ysifly_splashAd:didJumpWithSuccess:点击跳转结果;SDK 负责跳转及回退,媒体不要再次打开链接
ysifly_splashAdDidPlayFinish:视频播放完成,广告仍可保留末帧与交互,不表示关闭
ysifly_splashAd:didFailWithError:统一加载/展示失败出口,记录错误并继续宿主流程

可左右滚动查看完整表格

回调在主线程触发。强持有广告对象,并在展示前核对回调中的对象是否仍是当前实例。展示时传入已加入 window 的控制器;SDK 将开屏覆盖到窗口,无需宿主另外 present 广告控制器。

ysifly_isAdValid 仅表示实例当前是否可展示,不代替 Ready 和宿主可见性判断。成功展示后需要新建实例发起下一次请求。取消请求、宿主永久结束广告流程或释放页面时调用 ysifly_destroy;销毁后该对象不可恢复。不要仅因为广告打开了落地页就销毁仍在使用的广告。

媒体提供已布局且宽高有效的容器,并强持有广告。以下类在 loadBanner 中创建容器;实际业务可换为页面已有容器:

@interface BannerExampleViewController : UIViewController <YSIFLYBannerAdDelegate>
@property (nonatomic, strong) YSIFLYBannerAd *bannerAd;
@property (nonatomic, strong) UIView *bannerContainer;
@end

@implementation BannerExampleViewController
- (void)loadBanner {
    self.bannerAd.delegate = nil;
    [self.bannerAd ysifly_destroy];
    if (!self.bannerContainer) {
        self.bannerContainer = [[UIView alloc] initWithFrame:CGRectMake(16, 120, 288, 90)];
        [self.view addSubview:self.bannerContainer];
    }
    self.bannerAd = [[YSIFLYBannerAd alloc] initWithAdUnitId:@"YOUR_BANNER_AD_UNIT_ID"];
    self.bannerAd.delegate = self;
    self.bannerAd.currentViewController = self;
    self.bannerAd.closeButtonVisible = YES;
    [self.bannerAd ysifly_loadAd];
}
- (void)ysifly_bannerAdDidReady:(YSIFLYBannerAd *)ad {
    if (ad == self.bannerAd && [ad ysifly_isAdValid] && self.view.window) {
        [ad ysifly_showInView:self.bannerContainer];
    }
}
- (void)ysifly_bannerAd:(YSIFLYBannerAd *)ad didFailWithError:(YSIFLYAdError *)error {
    NSLog(@"Banner failed: %d %@", error.errorCode, error.errorDescription);
}
- (void)dealloc {
    [self.bannerAd ysifly_destroy];
}
@end

不要在同一 Banner 上循环 load/show;刷新广告时清理旧实例、创建新实例。容器布局与失败、关闭回调的完整处理见 Banner 示例。

插屏广告

在已有业务页面新建并持有插屏,收到 Ready 后在合适的业务间隙展示:

@interface InterstitialExampleViewController : UIViewController <YSIFLYInterstitialAdDelegate>
@property (nonatomic, strong) YSIFLYInterstitialAd *interstitialAd;
@end

@implementation InterstitialExampleViewController
- (void)loadInterstitial {
    self.interstitialAd.delegate = nil;
    [self.interstitialAd ysifly_destroy];
    self.interstitialAd = [[YSIFLYInterstitialAd alloc] initWithAdUnitId:@"YOUR_INTERSTITIAL_AD_UNIT_ID"];
    self.interstitialAd.delegate = self;
    self.interstitialAd.currentViewController = self;
    [self.interstitialAd ysifly_loadAd];
}
- (void)ysifly_interstitialAdDidReady:(YSIFLYInterstitialAd *)ad {
    if (ad != self.interstitialAd || ![ad ysifly_isAdValid] || !self.view.window) return;
    YSIFLYInterstitialAdConfig *config = [[YSIFLYInterstitialAdConfig alloc] init];
    config.presentationStyle = YSIFLYInterstitialPresentationStyleHalfScreen;
    config.muteOnStart = YES;
    [ad ysifly_showAdFromRootViewController:self config:config];
}
- (void)ysifly_interstitialAd:(YSIFLYInterstitialAd *)ad didFailWithError:(YSIFLYAdError *)error {
    NSLog(@"Interstitial failed: %d %@", error.errorCode, error.errorDescription);
}
- (void)dealloc {
    [self.interstitialAd ysifly_destroy];
}
@end

presentationStyle 可选 YSIFLYInterstitialPresentationStyleHalfScreen 或 YSIFLYInterstitialPresentationStyleFullScreen。展示前确认页面可见且没有冲突的模态展示;完整控制和日志见 插屏示例。

自渲染信息流

媒体负责依据 ad.adData 创建标题、图片、广告标识、CTA、关闭按钮和视频容器。SDK 负责广告请求、曝光、点击跳转及视频播放。强持有 YSIFLYNativeFeedAd 并设置 delegate 后调用 ysifly_loadAd;收到 ysifly_nativeFeedAdDidLoad: 后渲染媒体 UI,再调用 ysifly_attachWithViewBinder:error:。

素材和视图

公开数据/视图接入方式
materialType / templateId0 未知、1 单图、2 视频、3 多图;未知素材不展示
title、desc、content、ctaText、appName按实际非空字段布局,不假设每个文案都有值
mainImage / imageList媒体加载图片并处理失败;多图为两至三张
videoURL / videoCoverURL视频交给 SDK 播放,封面可由媒体展示
adSourceMark / brand渲染广告来源与广告标识
containerView广告整体容器,必须有效且按实际素材完成布局
renderViews已渲染的广告素材视图;与关闭、视频视图均位于容器内
videoView视频必填,用普通 UIView 承载 SDK 视频,无需创建 AVPlayer
clickViewsRedirect / Download 传实际点击视图;Exposure / Unknown 明确传 @[]

可左右滚动查看完整表格

下面的辅助函数放入媒体 .m 文件,在素材 UI 就绪后调用。传入的视图须已由媒体创建并放在 container 中;视频时 videoView 必填,可点击素材的 clickViews 必须非空。

static BOOL AttachNativeAd(YSIFLYNativeFeedAd *ad,
                           UIView *container,
                           NSArray<UIView *> *renderViews,
                           NSArray<UIView *> *clickViews,
                           UIView *videoView,
                           UIView *closeView,
                           YSIFLYAdError **error) {
    YSIFLYNativeFeedAdViewBinder *binder = [[YSIFLYNativeFeedAdViewBinder alloc] init];
    binder.containerView = container;
    binder.renderViews = renderViews;
    BOOL clickable = ad.adData.interactionType == YSIFLYNativeFeedAdInteractionTypeRedirect ||
                     ad.adData.interactionType == YSIFLYNativeFeedAdInteractionTypeDownload;
    binder.clickViews = clickable ? clickViews : @[];
    binder.videoView = videoView;
    binder.closeView = closeView;
    return [ad ysifly_attachWithViewBinder:binder error:error];
}

必须在主线程同步 attach,检查返回值和 YSIFLYAdError。clickViews = nil 会回退为整容器可点击,因此无点击行为应显式传空数组。SDK 负责跳转与失败回退,媒体不要对注册的 CTA 再添加自己的跳转或手动打开素材 URL。

列表复用和视频

  • 数据项强持有同一个 Ad;Cell 只渲染并挂载。didEndDisplaying、prepareForReuse 或换普通内容时,对回调提供的具体 Cell 容器同步调用 +[YSIFLYNativeFeedAd ysifly_detachAdFromContainerView:]。
  • detach 解除视图挂载,保留数据项中的广告;滚回屏幕后可再次 attach。同一逻辑广告不会因为滚回重复曝光。不要按旧 indexPath 反查广告,也不要延迟执行解绑。
  • ysifly_detachFromCurrentContainer 只用于固定、非复用且不迁移的单容器;列表必须按具体容器解绑。
  • 永久删除条目或退出页面时先 detach、置空 delegate,再释放最后一个 Ad 强引用;仍持有对象但希望主动终止时可调用 ysifly_destroy。
  • 广告过期后不能新挂载;遇到 71506 应淘汰旧条目并请求新广告。不能将一次 attach 失败简单等同于整个列表不可用。
  • 视频离屏/回屏可保留进度和播放意图。ysifly_pausePlay / ysifly_stopPlay 后,不会仅因回屏自动恢复;需要 ysifly_resumePlay / ysifly_startPlay 请求播放,实际起播仍须满足已挂载、已曝光等条件。

完整的异步图片加载、视频容器、Cell 复用和错误处理见 信息流示例。

容器外 CTA

默认 allowsExternalClickViews = NO。确需把 CTA 放到广告容器外时显式设为 YES;只放宽点击视图,渲染、关闭和视频视图仍须在容器中。

外部 CTA 可在 attach 后挂载或布局;实际点击时须与广告处于同一 window/scene,可见、可交互且尺寸有效,广告容器处于前台并至少有 2/3 可见。CTA 不能是 window、页面根视图或容器本身;不要让多个活动广告共享同一个 CTA。调整已挂载的点击视图集合或内外层级关系时,先 detach 再 attach。

拒绝点击通过 ysifly_nativeFeedAd:didRejectClickWithError: 返回 YSIFLYAdErrorCodeNativeFeedClickViewsInvalid(71503)。根据 errorDescription 中的 [71503/<point>] 定位视图问题;不要用手工曝光、补跳转或重复点击绕过拒绝。

YS 不提供媒体主动上报摇一摇点击的能力,ysifly_reportMediaShakeTriggeredWithError: 返回 NO 和 71512,不要把该方法作为此版本的摇一摇入口。