广告加载与展示
本文面向已完成安装的宿主,接口以所安装版本的 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;销毁后该对象不可恢复。不要仅因为广告打开了落地页就销毁仍在使用的广告。
Banner 广告
媒体提供已布局且宽高有效的容器,并强持有广告。以下类在 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];
}
@endpresentationStyle 可选 YSIFLYInterstitialPresentationStyleHalfScreen 或 YSIFLYInterstitialPresentationStyleFullScreen。展示前确认页面可见且没有冲突的模态展示;完整控制和日志见 插屏示例。
自渲染信息流
媒体负责依据 ad.adData 创建标题、图片、广告标识、CTA、关闭按钮和视频容器。SDK 负责广告请求、曝光、点击跳转及视频播放。强持有 YSIFLYNativeFeedAd 并设置 delegate 后调用 ysifly_loadAd;收到 ysifly_nativeFeedAdDidLoad: 后渲染媒体 UI,再调用 ysifly_attachWithViewBinder:error:。
素材和视图
| 公开数据/视图 | 接入方式 |
|---|---|
materialType / templateId | 0 未知、1 单图、2 视频、3 多图;未知素材不展示 |
title、desc、content、ctaText、appName | 按实际非空字段布局,不假设每个文案都有值 |
mainImage / imageList | 媒体加载图片并处理失败;多图为两至三张 |
videoURL / videoCoverURL | 视频交给 SDK 播放,封面可由媒体展示 |
adSourceMark / brand | 渲染广告来源与广告标识 |
containerView | 广告整体容器,必须有效且按实际素材完成布局 |
renderViews | 已渲染的广告素材视图;与关闭、视频视图均位于容器内 |
videoView | 视频必填,用普通 UIView 承载 SDK 视频,无需创建 AVPlayer |
clickViews | Redirect / 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,不要把该方法作为此版本的摇一摇入口。