前言git
因爲項目中要用到啓動頁廣告,因此作了簡單的研究,同時借鑑網易新聞和蘑菇街的交互寫了一個簡單的demo,如今寫出來供你們參考,可能因爲我的侷限會有一些bug和不完善的地方,也但願你們可以友善提醒和指正。github
Github地址:https://github.com/Running2snail/LLFullScreenAd緩存
效果圖以下:服務器
代碼分析:
上面主要展現了廣告圖提過按鈕顯示的兩種方式,一種是常見的計數倒計時+跳過的樣式(大部分的廣告啓動頁都是這種方式),一種是經過環形倒計時+跳過的樣式(仿網易新聞)。下面我將分別介紹兩種樣式的簡單原理。app
思路分析:
啓動頁廣告是在啓動頁消失後添加在window上顯示,過程爲獲取廣告圖信息,而後下載廣告圖,其次顯示並相應相應的點擊跳轉等事件。過程並不複雜,主要的問題在於啓動時期比較短暫並且圖片信息的獲取和下載時間的不肯定等問題。在這裏主要經過設置一個等待計時器來在避免長時間的等待,同時利用SDWebImage來緩存圖片數據用於下次的顯示。另外圓形進度條的實現也比較簡單,這裏就不作過多的介紹,下面具體看主要的代碼實現。async
代碼分析:oop
在AppDelegate.m文件中建立並添加到window上,同時下載廣告圖動畫
AppDelegate.matom
@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 = 3; 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.hurl
#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來實現。
上面的實現比較簡單,因此也沒有用太多的解釋,若是感興趣能夠看下源代碼,若是有好的建議或者疑問能夠留言探討。另外若是喜歡或者感受對你有幫助最好能給個Star,很是感謝。