前言
由于项目中要用到启动页广告,所以做了简单的研究,同时借鉴网易新闻和蘑菇街的交互写了一个简单的demo,现在写出来供大家参考,可能由于个人局限会有一些bug和不完善的地方,也希望大家能够友善提醒和指正。
效果图如下:
上面主要展示了广告图提过按钮显示的两种方式,一种是常见的计数倒计时+跳过的样式(大部分的广告启动页都是这种方式),一种是通过环形倒计时+跳过的样式(仿网易新闻)。
思路分析:
启动页广告是在启动页消失后添加在window上显示,过程为获取广告图信息,然后下载广告图,其次显示并相应相应的点击跳转等事件。过程并不复杂,主要的问题在于启动时期比较短暂而且图片信息的获取和下载时间的不确定等问题。在这里主要通过设置一个等待计时器来在避免长时间的等待,同时利用SDWebImage来缓存图片数据用于下次的显示。另外圆形进度条的实现也比较简单,这里就不做过多的介绍,下面具体看主要的代码实现。
代码分析:
在AppDelegate.m文件中创建并添加到window上,同时下载广告图
AppDelegate.m
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
self.window.rootViewController = [[ViewController alloc] init];
[self.window makeKeyAndVisible];
[self addADView]; // 添加广告图
[self getADImageURL];
return YES;
}
/** 添加广告图 */
- (void)addADView
{
LLFullScreenAdView *adView = [[LLFullScreenAdView alloc] init];
adView.tag = 100;
adView.duration = 5;
adView.waitTime = 5;
adView.skipType = SkipButtonTypeCircleAnimationTest;
adView.adImageTapBlock = ^(NSString *content) {
NSLog(@"%@", content);
};
[self.window addSubview:adView];
}
/** 获取广告图URL */
- (void)getADImageURL
{
// 此处推荐使用tag来获取adView,勿使用全局变量。因为在AppDelegate中将其设为全局变量时,不会被释放
LLFullScreenAdView *adView = (LLFullScreenAdView *)[self.window viewWithTag:100];
// 模拟从服务器上获取广告图URL
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSString *urlString = @"http://s8.mogucdn.com/p2/170223/28n_4eb3la6b6b0h78c23d2kf65dj1a92_750x1334.jpg";
[adView reloadAdImageWithUrl:urlString]; // 加载广告图(如果没有设置广告图设置为空)
});
}
LLFullScreenAdView.h
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSUInteger, SkipButtonType) {
SkipButtonTypeNormalTimeAndText = 0, //普通的倒计时+跳过
SkipButtonTypeCircleAnimationTest, //圆形动画+跳过
SkipButtonTypeNormalText, //只有普通的跳过
SkipButtonTypeNormalTime, //只有普通的倒计时
SkipButtonTypeNone //无
};
typedef void(^adImageBlock)(NSString *content); //可以根据需要添加一些相应的参数
@interface LLFullScreenAdView : UIImageView
/** 广告图的显示时间(默认5秒)*/
@property (nonatomic, assign) NSUInteger duration;
/** 获取数据前,启动图的等待时间(若不设置则不启动等待机制)*/
@property (nonatomic, assign) NSUInteger waitTime;
/** 右上角按钮的样式(默认倒计时+跳过)*/
@property (nonatomic, assign) SkipButtonType skipType;
/** 广告图点击事件回调*/
@property (nonatomic, copy) adImageBlock adImageTapBlock;
/** 加载广告图*/
- (void)reloadAdImageWithUrl:(NSString *)urlStr;
@end
LLFullScreenAdView.m
/** 获取启动图片 */
- (UIImage *)getLaunchImage
{
CGSize viewSize = [UIScreen mainScreen].bounds.size;
NSString *viewOrientation = @"Portrait"; // 横屏请设置成 @"Landscape"
UIImage *lauchImage = nil;
NSArray *imagesDictionary = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"UILaunchImages"];
for (NSDictionary *dict in imagesDictionary)
{
CGSize imageSize = CGSizeFromString(dict[@"UILaunchImageSize"]);
if (CGSizeEqualToSize(imageSize, viewSize) && [viewOrientation isEqualToString:dict[@"UILaunchImageOrientation"]])
{
lauchImage = [UIImage imageNamed:dict[@"UILaunchImageName"]];
}
}
return lauchImage;
}
/** 广告图加载前等待计时器 */
- (void)scheduledWaitTimer
{
if (_timerWait) dispatch_source_cancel(_timerWait);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timerWait = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timerWait, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timerWait, ^{
if (_waitTime <= 0) {
_flag = YES;
dispatch_source_cancel(_timerWait);
dispatch_async(dispatch_get_main_queue(), ^{
[self dismiss]; // 关闭界面
});
} else {
_waitTime--;
}
});
dispatch_resume(_timerWait);
}
/** 获取广告图 */
- (void)reloadAdImageWithUrl:(NSString *)urlStr
{
if (urlStr.length <= 0) {
if (_timerWait) dispatch_source_cancel(_timerWait);
[self removeFromSuperview];
return;
}
NSURL *imageUrl = [NSURL URLWithString:urlStr];
__weak typeof(self) weakSelf = self;
UIImage *cacheImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:urlStr];
if (cacheImage) { //查看是否有缓存
NSLog(@"cacheImage");
[weakSelf adImageShowWithImage:cacheImage];
} else {
NSLog(@"noCacheImage");
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadImageWithURL:imageUrl options:SDWebImageLowPriority progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image && finished && error == nil) {
[weakSelf adImageShowWithImage:image];
[[SDImageCache sharedImageCache] storeImage:image forKey:urlStr toDisk:YES];
}
}];
}
}
/** 广告图显示倒计时 */
- (void)scheduledTimer
{
if (_timerWait) dispatch_source_cancel(_timerWait);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0 * NSEC_PER_SEC, 0); // 每秒执行
dispatch_source_set_event_handler(_timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (_duration <= 0) {
dispatch_source_cancel(_timer);
[self dismiss]; // 关闭界面
} else {
[self showSkipBtnTitleTime:_duration];
_duration--;
}
});
});
dispatch_resume(_timer);
}
/** 消失广告图 */
- (void)dismiss
{
NSLog(@"dismiss");
[UIView animateWithDuration:0.5 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.transform = CGAffineTransformMakeScale(1.2, 1.2);
self.alpha = 0.0;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
圆形进度条的简单实现
使用了CAShapeLayer的strokeStart和strokeEnd属性,实现起来十分的简单方便,通过设置Path的中心点、起始和终止的位置,并利用strokeStart和strokeEnd这两个属性支持动画的特点,我们通过以下代码就可以实现圆形进度条的效果
//懒加载一个计时器视图,并设置动画绘制的路径
- (UIView *)timerView
{
if (!_timerView) {
self.timerView = [[UIView alloc] initWithFrame:CGRectMake(MainScreenWidth - 62, 32, 40, 40)];
CAShapeLayer *layer = [CAShapeLayer layer];
layer.fillColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.4].CGColor; // 填充颜色
layer.strokeColor = [UIColor redColor].CGColor; // 绘制颜色
layer.lineCap = kCALineCapRound;
layer.lineJoin = kCALineJoinRound;
layer.lineWidth = 2;
layer.frame = self.bounds;
layer.path = [self getCirclePath].CGPath;
layer.strokeStart = 0;
[_timerView.layer addSublayer:layer];
self.viewLayer = layer;
UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(3, 3, 34, 34)];
titleLabel.text = @"跳过";
titleLabel.textColor = [UIColor whiteColor];
[titleLabel setTextAlignment:NSTextAlignmentCenter];
titleLabel.font = [UIFont systemFontOfSize:15];
[_timerView addSubview:titleLabel];
_remain = _duration * 20;
_count = 0;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(skipAction)];
[_timerView addGestureRecognizer:tap];
}
return _timerView;
}
/* 绘制路径
* path这个属性必须需要设置,不然是没有效果的/
*/
- (UIBezierPath *)getCirclePath
{
return [UIBezierPath bezierPathWithArcCenter:CGPointMake(20, 20) radius:19 startAngle:-0.5*M_PI endAngle:1.5*M_PI clockwise:YES];
}
/** 广告图显示倒计时 */
- (void)setCircleTimer
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 0.05 * NSEC_PER_SEC, 0); // 每秒执行
dispatch_source_set_event_handler(_timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (_count >= _remain) {
dispatch_source_cancel(_timer);
self.viewLayer.strokeStart = 1;
[self dismiss]; // 关闭界面
} else {
self.viewLayer.strokeStart += 0.01;
_count++; //剩余时间进行自加
}
});
});
dispatch_resume(_timer);
}
如果想对CAShapeLayer做更多了解可参考这篇文章上手CAShapeLayer,动画其实并不难
常用的计时器有两种一种是NSTimer,另一种是GCD,但NSTimer受runloop的影响,由于runloop需要处理很多任务,导致NSTimer的精度降低,在日常开发中,如果我们需要对定时器的精度要求很高的话,可以考虑dispatch_source_t去实现 。dispatch_source_t精度很高,系统自动触发,是系统级别的源。而且使用 NSTimer 时,如果使用不当就容易出现内存泄露,所以计时器建议使用GCD来实现。