IFLYNativeFeedListViewController.m
IFLYADLibSimple/IFLYADLibSimple/biz/native/IFLYNativeFeedListViewController.m
#import "IFLYNativeFeedListViewController.h"
#import "IFLYADUtil.h"
#import <IFLYAdCore/IFLYAdCore.h>
#import <IFLYAdNativeFeed/IFLYAdNativeFeed.h>
static NSInteger const IFLYNativeFeedListAdRow = 4;
static NSInteger const IFLYNativeFeedListRowCount = 13;
static NSString *const IFLYNativeFeedListAdItemID = @"native-feed-list-ad-1";
static NSString *const IFLYNativeFeedListContentCellID = @"native-feed-list-content";
static NSString *const IFLYNativeFeedListAdCellID = @"native-feed-list-ad";
@class IFLYNativeFeedListCell;
/// 生产接入可把该模型扩展为字典/缓存;稳定 item ID 只需持有 Ad。
@interface IFLYNativeFeedListItem : NSObject
@property (nonatomic, copy) NSString *itemIdentifier;
@property (nonatomic, strong, nullable) IFLYNativeFeedAd *ad;
@property (nonatomic, strong, nullable) UIImage *coverImage;
@property (nonatomic, assign) BOOL presentationReady;
@property (nonatomic, assign) NSUInteger generation;
@end
@implementation IFLYNativeFeedListItem
@end
/// Cell 保存媒体 UI 和对应条目标识;广告对象由列表数据层持有。
@interface IFLYNativeFeedListCell : UITableViewCell
@property (nonatomic, copy, nullable) NSString *representedItemIdentifier;
@property (nonatomic, strong) UIView *cardView;
@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UILabel *descLabel;
@property (nonatomic, strong) UIView *mediaView;
@property (nonatomic, strong) UIImageView *coverImageView;
@property (nonatomic, strong) UILabel *mediaLabel;
@property (nonatomic, strong) UILabel *adBadgeLabel;
@property (nonatomic, strong) UIButton *ctaButton;
@property (nonatomic, strong) UIButton *closeButton;
- (BOOL)attachAd:(IFLYNativeFeedAd *)ad
itemIdentifier:(NSString *)itemIdentifier
coverImage:(nullable UIImage *)coverImage
error:(IFLYAdError *_Nullable *_Nullable)error;
- (void)detachAd;
- (void)setVideoCoverHidden:(BOOL)hidden text:(nullable NSString *)text;
@end
@implementation IFLYNativeFeedListCell
- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
self.selectionStyle = UITableViewCellSelectionStyleNone;
[self buildCard];
}
return self;
}
- (void)buildCard {
self.cardView = [[UIView alloc] initWithFrame:CGRectZero];
self.cardView.backgroundColor = UIColor.whiteColor;
self.cardView.layer.cornerRadius = 10;
self.cardView.layer.borderWidth = 1;
self.cardView.layer.borderColor = [UIColor colorWithWhite:0.86 alpha:1].CGColor;
self.cardView.clipsToBounds = YES;
[self.contentView addSubview:self.cardView];
self.titleLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
self.titleLabel.textColor = [UIColor colorWithWhite:0.12 alpha:1];
[self.cardView addSubview:self.titleLabel];
self.descLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.descLabel.font = [UIFont systemFontOfSize:13];
self.descLabel.textColor = [IFLYADUtil demoSecondaryLabelColor];
self.descLabel.numberOfLines = 2;
[self.cardView addSubview:self.descLabel];
self.mediaView = [[UIView alloc] initWithFrame:CGRectZero];
self.mediaView.backgroundColor = [UIColor colorWithWhite:0.12 alpha:1];
self.mediaView.layer.cornerRadius = 8;
self.mediaView.clipsToBounds = YES;
[self.cardView addSubview:self.mediaView];
self.coverImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
self.coverImageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.coverImageView.contentMode = UIViewContentModeScaleAspectFill;
self.coverImageView.clipsToBounds = YES;
[self.mediaView addSubview:self.coverImageView];
self.mediaLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.mediaLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.mediaLabel.font = [UIFont systemFontOfSize:13];
self.mediaLabel.textAlignment = NSTextAlignmentCenter;
self.mediaLabel.textColor = [UIColor colorWithWhite:0.75 alpha:1];
[self.mediaView addSubview:self.mediaLabel];
self.adBadgeLabel = [[UILabel alloc] initWithFrame:CGRectZero];
self.adBadgeLabel.font = [UIFont systemFontOfSize:10];
self.adBadgeLabel.textAlignment = NSTextAlignmentCenter;
self.adBadgeLabel.textColor = UIColor.whiteColor;
self.adBadgeLabel.backgroundColor = [UIColor colorWithWhite:0.25 alpha:0.7];
self.adBadgeLabel.layer.cornerRadius = 4;
self.adBadgeLabel.clipsToBounds = YES;
[self.cardView addSubview:self.adBadgeLabel];
self.ctaButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.ctaButton.titleLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightMedium];
self.ctaButton.backgroundColor = [IFLYADUtil demoIndigoColor];
self.ctaButton.layer.cornerRadius = 5;
self.ctaButton.clipsToBounds = YES;
[self.ctaButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[self.cardView addSubview:self.ctaButton];
self.closeButton = [UIButton buttonWithType:UIButtonTypeSystem];
self.closeButton.titleLabel.font = [UIFont systemFontOfSize:16 weight:UIFontWeightSemibold];
self.closeButton.backgroundColor = [UIColor colorWithWhite:0.25 alpha:0.7];
self.closeButton.layer.cornerRadius = 13;
self.closeButton.clipsToBounds = YES;
[self.closeButton setTitle:@"×" forState:UIControlStateNormal];
[self.closeButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
[self.cardView addSubview:self.closeButton];
}
- (void)layoutSubviews {
[super layoutSubviews];
CGFloat cardWidth = CGRectGetWidth(self.contentView.bounds) - 24;
self.cardView.frame = CGRectMake(12, 8, cardWidth, CGRectGetHeight(self.contentView.bounds) - 16);
CGFloat innerWidth = cardWidth - 24;
self.titleLabel.frame = CGRectMake(12, 10, innerWidth, 22);
self.descLabel.frame = CGRectMake(12, 34, innerWidth, 34);
self.mediaView.frame = CGRectMake(12, 74, innerWidth, 174);
self.coverImageView.frame = self.mediaView.bounds;
self.mediaLabel.frame = self.mediaView.bounds;
CGFloat footerY = CGRectGetMaxY(self.mediaView.frame) + 9;
self.adBadgeLabel.frame = CGRectMake(12, footerY + 3, 42, 20);
self.closeButton.frame = CGRectMake(cardWidth - 38, footerY, 26, 26);
self.ctaButton.frame = CGRectMake(CGRectGetMinX(self.closeButton.frame) - 82, footerY, 72, 26);
}
- (BOOL)attachAd:(IFLYNativeFeedAd *)ad
itemIdentifier:(NSString *)itemIdentifier
coverImage:(nullable UIImage *)coverImage
error:(IFLYAdError *_Nullable *_Nullable)error {
// 可见行 reload 可能不触发 prepareForReuse;修改媒体 UI 前先按容器反注册。
[self detachAd];
self.representedItemIdentifier = itemIdentifier;
IFLYNativeFeedAdData *data = ad.adData;
if (!data || !data.isMaterialComplete ||
data.materialType == IFLYNativeFeedAdMaterialTypeUnknown) {
return NO;
}
[self configureWithAdData:data itemIdentifier:itemIdentifier coverImage:coverImage];
BOOL isVideo = data.materialType == IFLYNativeFeedAdMaterialTypeVideo;
BOOL clickable = data.interactionType == IFLYNativeFeedAdInteractionTypeRedirect ||
data.interactionType == IFLYNativeFeedAdInteractionTypeDownload;
NSMutableArray<UIView *> *renderViews = [NSMutableArray arrayWithArray:@[
self.titleLabel, self.descLabel, self.mediaView, self.adBadgeLabel, self.closeButton
]];
if (!self.ctaButton.hidden) {
[renderViews addObject:self.ctaButton];
}
IFLYNativeFeedAdViewBinder *binder = [[IFLYNativeFeedAdViewBinder alloc] init];
binder.containerView = self.cardView;
binder.renderViews = renderViews.copy;
binder.clickViews = clickable ? @[self.cardView] : @[];
binder.closeView = self.closeButton;
binder.videoView = isVideo ? self.mediaView : nil;
binder.titleView = self.titleLabel;
binder.descView = self.descLabel;
binder.imageView = isVideo ? nil : self.mediaView;
binder.adSourceView = self.adBadgeLabel;
binder.ctaView = self.ctaButton.hidden ? nil : self.ctaButton;
if (![ad attachWithViewBinder:binder error:error]) {
[IFLYNativeFeedAd detachAdFromContainerView:self.cardView];
[self resetPresentation];
return NO;
}
return YES;
}
- (void)configureWithAdData:(IFLYNativeFeedAdData *)data
itemIdentifier:(NSString *)itemIdentifier
coverImage:(nullable UIImage *)coverImage {
[self resetPresentation];
self.representedItemIdentifier = itemIdentifier;
self.titleLabel.text = data.title.length > 0 ? data.title :
(data.appName.length > 0 ? data.appName :
(data.brand.length > 0 ? data.brand : @"广告"));
self.descLabel.text = data.desc.length > 0 ? data.desc :
(data.content.length > 0 ? data.content : @"");
self.adBadgeLabel.text = data.adSourceMark.length > 0 ? data.adSourceMark : @"广告";
self.adBadgeLabel.hidden = NO;
self.closeButton.hidden = NO;
BOOL clickable = data.interactionType == IFLYNativeFeedAdInteractionTypeRedirect ||
data.interactionType == IFLYNativeFeedAdInteractionTypeDownload;
self.ctaButton.hidden = !clickable;
NSString *fallbackCTA = data.interactionType == IFLYNativeFeedAdInteractionTypeDownload ? @"立即下载" : @"查看详情";
[self.ctaButton setTitle:(clickable ? (data.ctaText.length > 0 ? data.ctaText : fallbackCTA) : nil)
forState:UIControlStateNormal];
BOOL isVideo = data.materialType == IFLYNativeFeedAdMaterialTypeVideo;
self.mediaLabel.text = isVideo ? @"视频加载中" :
(data.materialType == IFLYNativeFeedAdMaterialTypeMultipleImages ? @"多图素材(完整渲染见基础示例)" : @"图片加载中");
self.coverImageView.image = coverImage;
self.mediaLabel.hidden = coverImage != nil;
}
- (void)setVideoCoverHidden:(BOOL)hidden text:(nullable NSString *)text {
self.coverImageView.hidden = hidden;
self.mediaLabel.hidden = hidden;
if (!hidden && text.length > 0) {
self.mediaLabel.text = text;
}
}
- (void)detachAd {
[IFLYNativeFeedAd detachAdFromContainerView:self.cardView];
[self resetPresentation];
}
- (void)resetPresentation {
self.titleLabel.text = @"";
self.descLabel.text = @"";
self.adBadgeLabel.hidden = YES;
self.ctaButton.hidden = YES;
self.closeButton.hidden = YES;
self.coverImageView.image = nil;
self.coverImageView.hidden = NO;
self.mediaLabel.hidden = NO;
self.mediaLabel.text = @"等待广告";
}
- (void)prepareForReuse {
[super prepareForReuse];
[self detachAd];
self.representedItemIdentifier = nil;
}
@end
@interface IFLYNativeFeedListViewController () <UITableViewDataSource, UITableViewDelegate, IFLYNativeFeedAdDelegate>
@property (nonatomic, strong) UITableView *tableView;
@property (nonatomic, strong) UILabel *statusLabel;
@property (nonatomic, copy) NSArray<NSString *> *itemIdentifiers;
@property (nonatomic, strong) NSMutableDictionary<NSString *, IFLYNativeFeedListItem *> *itemsByIdentifier;
@property (nonatomic, weak, nullable) IFLYNativeFeedListCell *visibleAdCell;
@property (nonatomic, weak, nullable) IFLYNativeFeedListCell *attachedAdCell;
@property (nonatomic, copy, nullable) NSString *visibleAdItemIdentifier;
- (void)startLoadingItem:(IFLYNativeFeedListItem *)item;
- (BOOL)attachItem:(IFLYNativeFeedListItem *)item toCell:(IFLYNativeFeedListCell *)cell;
- (void)continueItemAfterCellDetached:(nullable IFLYNativeFeedListItem *)item;
- (void)evictItem:(nullable IFLYNativeFeedListItem *)item;
- (nullable IFLYNativeFeedListItem *)itemForAd:(IFLYNativeFeedAd *)ad;
@end
@implementation IFLYNativeFeedListViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"信息流列表复用";
self.view.backgroundColor = UIColor.whiteColor;
[self buildStableDataSource];
[self buildTableView];
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:@"淘汰广告"
style:UIBarButtonItemStylePlain
target:self
action:@selector(evictCurrentAdItem)];
}
- (void)dealloc {
for (IFLYNativeFeedListItem *item in self.itemsByIdentifier.allValues) {
[self evictItem:item];
}
}
- (void)buildStableDataSource {
NSMutableArray<NSString *> *identifiers = [NSMutableArray arrayWithCapacity:IFLYNativeFeedListRowCount];
for (NSInteger row = 0; row < IFLYNativeFeedListRowCount; row++) {
[identifiers addObject:row == IFLYNativeFeedListAdRow
? IFLYNativeFeedListAdItemID
: [NSString stringWithFormat:@"content-%ld", (long)row]];
}
self.itemIdentifiers = identifiers.copy;
IFLYNativeFeedListItem *adItem = [[IFLYNativeFeedListItem alloc] init];
adItem.itemIdentifier = IFLYNativeFeedListAdItemID;
self.itemsByIdentifier = [NSMutableDictionary dictionaryWithObject:adItem forKey:adItem.itemIdentifier];
}
- (void)buildTableView {
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
[self.tableView registerClass:UITableViewCell.class forCellReuseIdentifier:IFLYNativeFeedListContentCellID];
[self.tableView registerClass:IFLYNativeFeedListCell.class forCellReuseIdentifier:IFLYNativeFeedListAdCellID];
[self.view addSubview:self.tableView];
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 104)];
UILabel *explanation = [[UILabel alloc] initWithFrame:CGRectMake(16, 8, CGRectGetWidth(header.bounds) - 32, 58)];
explanation.autoresizingMask = UIViewAutoresizingFlexibleWidth;
explanation.numberOfLines = 3;
explanation.font = [UIFont systemFontOfSize:13];
explanation.textColor = [IFLYADUtil demoSecondaryLabelColor];
explanation.text = @"滚动到广告行查看素材,滑出后再滑回可恢复同一条广告。点“淘汰广告”结束该条广告;接入时由数据层持有 Ad,Cell 负责挂载与解绑。";
[header addSubview:explanation];
self.statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(16, 70, CGRectGetWidth(header.bounds) - 32, 24)];
self.statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth;
self.statusLabel.font = [UIFont systemFontOfSize:13 weight:UIFontWeightMedium];
self.statusLabel.textColor = [IFLYADUtil demoIndigoColor];
self.statusLabel.text = @"滚动到第 5 行加载广告";
[header addSubview:self.statusLabel];
self.tableView.tableHeaderView = header;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
(void)tableView;
(void)section;
return self.itemIdentifiers.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *itemIdentifier = [self itemIdentifierAtIndexPath:indexPath];
if ([itemIdentifier isEqualToString:IFLYNativeFeedListAdItemID]) {
return [tableView dequeueReusableCellWithIdentifier:IFLYNativeFeedListAdCellID forIndexPath:indexPath];
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:IFLYNativeFeedListContentCellID
forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = [NSString stringWithFormat:@"普通内容 · %@", itemIdentifier];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
(void)tableView;
return [[self itemIdentifierAtIndexPath:indexPath] isEqualToString:IFLYNativeFeedListAdItemID] ? 318 : 64;
}
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath {
(void)tableView;
NSString *itemIdentifier = [self itemIdentifierAtIndexPath:indexPath];
IFLYNativeFeedListItem *item = self.itemsByIdentifier[itemIdentifier];
if (!item || ![cell isKindOfClass:IFLYNativeFeedListCell.class]) {
return;
}
IFLYNativeFeedListCell *adCell = (IFLYNativeFeedListCell *)cell;
adCell.representedItemIdentifier = itemIdentifier;
self.visibleAdCell = adCell;
self.visibleAdItemIdentifier = itemIdentifier;
if (item.ad) {
if (!item.presentationReady) {
return;
}
if ([self attachItem:item toCell:adCell]) {
return;
}
// TTL / 视频截止时间只限制下一次挂载。旧 Cell 尚未 didEndDisplaying 时,
// 先让它正常 detach,不因新 Cell 的挂载失败提前结束稳定广告条目。
if (self.attachedAdCell && self.attachedAdCell != adCell) {
return;
}
[self evictItem:item];
adCell.representedItemIdentifier = itemIdentifier;
self.visibleAdCell = adCell;
self.visibleAdItemIdentifier = itemIdentifier;
}
[self startLoadingItem:item];
}
- (void)tableView:(UITableView *)tableView
didEndDisplayingCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath {
(void)tableView;
(void)indexPath;
if (![cell isKindOfClass:IFLYNativeFeedListCell.class]) {
return;
}
// 回调 indexPath 可能已过时;只按回调 Cell 的容器反注册。
IFLYNativeFeedListCell *adCell = (IFLYNativeFeedListCell *)cell;
NSString *itemIdentifier = adCell.representedItemIdentifier;
IFLYNativeFeedListItem *item = self.itemsByIdentifier[itemIdentifier];
[adCell detachAd];
if (self.attachedAdCell == adCell) {
self.attachedAdCell = nil;
}
if (self.visibleAdCell == adCell && [self.visibleAdItemIdentifier isEqualToString:itemIdentifier]) {
self.visibleAdCell = nil;
self.visibleAdItemIdentifier = nil;
}
[self continueItemAfterCellDetached:item];
}
- (nullable NSString *)itemIdentifierAtIndexPath:(NSIndexPath *)indexPath {
return indexPath.row >= 0 && indexPath.row < (NSInteger)self.itemIdentifiers.count
? self.itemIdentifiers[indexPath.row]
: nil;
}
- (void)startLoadingItem:(IFLYNativeFeedListItem *)item {
if (!item || item.ad) {
return;
}
item.generation += 1;
IFLYNativeFeedAd *ad = [[IFLYNativeFeedAd alloc] initWithAdUnitId:__TYPED_ONE_NATIVE_AD_UNIT_ID__];
ad.delegate = self;
ad.currentViewController = self;
ad.muteOnStart = YES;
item.ad = ad;
self.statusLabel.text = @"正在加载稳定广告条目";
[ad loadAdWithRequestConfig:[IFLYADUtil mediaSampleRequestConfig]];
}
- (BOOL)attachItem:(IFLYNativeFeedListItem *)item toCell:(IFLYNativeFeedListCell *)cell {
if (!item || !cell || !item.ad || !item.presentationReady) {
return NO;
}
// 重复 willDisplay 不改写子视图;SDK 的同 Ad/同容器挂载本身也是幂等的。
if (self.attachedAdCell == cell) {
return YES;
}
IFLYAdError *error = nil;
BOOL attached = [cell attachAd:item.ad
itemIdentifier:item.itemIdentifier
coverImage:item.coverImage
error:&error];
self.statusLabel.text = attached
? @"已挂载:离屏按容器 detach,回屏使用同一 Ad"
: [NSString stringWithFormat:@"挂载等待/失败:%@",
error ? [IFLYADUtil summaryForError:error] : @"未知错误"];
if (attached) {
self.attachedAdCell = cell;
}
return attached;
}
- (void)continueItemAfterCellDetached:(nullable IFLYNativeFeedListItem *)item {
if (!item || !item.ad || !item.presentationReady) {
return;
}
IFLYNativeFeedListCell *visibleCell = self.visibleAdCell;
BOOL hasWaitingVisibleCell =
visibleCell && visibleCell != self.attachedAdCell &&
[self.visibleAdItemIdentifier isEqualToString:item.itemIdentifier];
if (!hasWaitingVisibleCell) {
return;
}
if ([self attachItem:item toCell:visibleCell] ||
(self.attachedAdCell && self.attachedAdCell != visibleCell)) {
return;
}
// 旧容器已正常 detach,但原 Ad 仍无法挂载(例如已过期);释放它并请求新广告。
[self evictItem:item];
if (self.visibleAdCell == visibleCell &&
[self.visibleAdItemIdentifier isEqualToString:item.itemIdentifier]) {
[self startLoadingItem:item];
}
}
- (void)evictCurrentAdItem {
IFLYNativeFeedListItem *item = self.itemsByIdentifier[IFLYNativeFeedListAdItemID];
[self evictItem:item];
self.statusLabel.text = @"已淘汰:detach 容器并释放最后一个 Ad 引用";
if (self.visibleAdCell && [self.visibleAdItemIdentifier isEqualToString:item.itemIdentifier]) {
self.visibleAdCell.representedItemIdentifier = item.itemIdentifier;
[self startLoadingItem:item];
}
}
- (void)evictItem:(nullable IFLYNativeFeedListItem *)item {
if (!item) {
return;
}
IFLYNativeFeedAd *ad = item.ad;
IFLYNativeFeedListCell *attachedCell = self.attachedAdCell;
IFLYNativeFeedListCell *visibleCell = self.visibleAdCell;
item.generation += 1;
item.ad = nil;
item.coverImage = nil;
item.presentationReady = NO;
self.attachedAdCell = nil;
ad.delegate = nil;
[attachedCell detachAd];
if (visibleCell != attachedCell) {
[visibleCell detachAd];
}
// 普通淘汰不必显式 destroy;item.ad 与局部变量释放后,SDK 由最后一个 Ad 强引用的析构收口资源。
}
- (nullable IFLYNativeFeedListItem *)itemForAd:(IFLYNativeFeedAd *)ad {
for (IFLYNativeFeedListItem *item in self.itemsByIdentifier.allValues) {
if (item.ad == ad) {
return item;
}
}
return nil;
}
- (void)nativeFeedAdDidLoad:(IFLYNativeFeedAd *)ad {
IFLYNativeFeedListItem *item = [self itemForAd:ad];
if (!item) {
return;
}
IFLYNativeFeedAdData *data = ad.adData;
BOOL imageRequired = data.materialType != IFLYNativeFeedAdMaterialTypeVideo;
NSString *coverURL = data.materialType == IFLYNativeFeedAdMaterialTypeVideo
? data.videoCoverURL
: data.imageURLs.firstObject;
if (coverURL.length == 0 && imageRequired) {
self.statusLabel.text = @"图片素材地址为空,已淘汰当前广告";
[self evictItem:item];
return;
}
NSUInteger generation = item.generation;
__weak typeof(self) weakSelf = self;
void (^finishPresentation)(UIImage *_Nullable, NSError *_Nullable) =
^(UIImage *_Nullable image, NSError *_Nullable imageError) {
__strong typeof(weakSelf) self = weakSelf;
if (!self) {
return;
}
IFLYNativeFeedListItem *currentItem = [self itemForAd:ad];
if (currentItem != item || item.generation != generation || item.ad != ad) {
return;
}
if (!image && imageRequired) {
NSString *failureDescription = imageError.localizedDescription.length > 0
? imageError.localizedDescription
: @"图片数据无效";
self.statusLabel.text = [NSString stringWithFormat:@"图片加载失败,已淘汰:%@",
failureDescription];
[self evictItem:item];
return;
}
item.coverImage = image;
item.presentationReady = YES;
self.statusLabel.text = @"素材就绪,数据层只持有 Ad";
if (self.visibleAdCell &&
[self.visibleAdItemIdentifier isEqualToString:item.itemIdentifier] &&
self.visibleAdCell != self.attachedAdCell) {
[self attachItem:item toCell:self.visibleAdCell];
}
};
if (coverURL.length == 0) {
finishPresentation(nil, nil);
} else {
self.statusLabel.text = @"加载成功,正在准备媒体素材";
[IFLYADUtil loadImageWithURLString:coverURL completion:finishPresentation];
}
}
- (void)nativeFeedAd:(IFLYNativeFeedAd *)ad didFailWithError:(IFLYAdError *)error {
IFLYNativeFeedListItem *item = [self itemForAd:ad];
if (!item) {
return;
}
self.statusLabel.text = [NSString stringWithFormat:@"加载失败:%@", [IFLYADUtil summaryForError:error]];
[self evictItem:item];
}
- (void)nativeFeedAdDidClose:(IFLYNativeFeedAd *)ad {
IFLYNativeFeedListItem *item = [self itemForAd:ad];
if (item) {
self.statusLabel.text = @"广告已关闭并淘汰";
[self evictItem:item];
}
}
- (void)nativeFeedAdDidRender:(IFLYNativeFeedAd *)ad {
if ([self itemForAd:ad] && self.visibleAdCell == self.attachedAdCell) {
self.statusLabel.text = @"当前 Cell 挂载成功";
// 列表重挂载不能重复 startPlay,否则会覆盖离屏前显式 pause/stop 的播放意图。
// SDK 内部按同一 Ad 保留播放进度与意图。
}
}
- (void)nativeFeedAdDidExpose:(IFLYNativeFeedAd *)ad {
if ([self itemForAd:ad]) {
self.statusLabel.text = @"广告已曝光;再次回屏不会重复曝光";
}
}
- (void)nativeFeedAdDidStartPlay:(IFLYNativeFeedAd *)ad {
if ([self itemForAd:ad] && self.visibleAdCell == self.attachedAdCell) {
[self.visibleAdCell setVideoCoverHidden:YES text:nil];
}
}
- (void)nativeFeedAdDidResumePlay:(IFLYNativeFeedAd *)ad {
if ([self itemForAd:ad] && self.visibleAdCell == self.attachedAdCell) {
[self.visibleAdCell setVideoCoverHidden:YES text:nil];
}
}
- (void)nativeFeedAdDidPausePlay:(IFLYNativeFeedAd *)ad {
if ([self itemForAd:ad] && self.visibleAdCell == self.attachedAdCell) {
[self.visibleAdCell setVideoCoverHidden:NO text:@"视频已暂停"];
}
}
- (void)nativeFeedAdDidPlayFinish:(IFLYNativeFeedAd *)ad {
if ([self itemForAd:ad] && self.visibleAdCell == self.attachedAdCell) {
[self.visibleAdCell setVideoCoverHidden:NO text:@"视频播放完成"];
}
}
- (void)nativeFeedAd:(IFLYNativeFeedAd *)ad didFailToPlayWithError:(IFLYAdError *)error {
(void)error;
if ([self itemForAd:ad] && self.visibleAdCell == self.attachedAdCell) {
[self.visibleAdCell setVideoCoverHidden:NO text:@"视频播放失败"];
}
}
@end