前言
广告轮播图如今早已是iOS应用的标配了,似乎任何一款App的首页都会有一个广告轮播图。
本文的目的就是要将App里面的广告轮播图封装成一个独立模块,以便简化开发过程。
如果你对独立“封装一个自己的广告轮播图”感兴趣,欢迎继续读下去。
轮播图效果
为了从开始讲述整个动手封装轮播图的过程,我们先从简单的开始,后期我会一步步把功能封装的更加完善起来,欢迎去我的github上去下载完整代码,如果有什么问题更欢迎随时issue我。
轮播图分析
当我们做一个很独立的功能时候可能会感觉内部功能较多而感觉无从下手,下面我们就先分析一下这个轮播图主要部件。
轮播图“原料”
- UIScrollView
- UIPageControl
- UIImageView
思路
- 图片放到ScrollView上水平排列
- 放置小圆点(pageControl)标识位置
- 开启定时滚动功能
有了小的思路,那就开始一点点做,(这里只是简单思路,做的过程中会根据遇到的问题一点点改善)
创建基本组件
先把基本的框架和布局搭建起来
这一步需要添加页面控制,标识是哪个展示图片的位置
不过在加入之前,先引入一个封装思想
封装思想
到这里就出现了一个问题,我们的功能和内容在变多,如果还像刚才这个ScrollView一样直接拖过去使用的话虽然没有问题,但是想改变整个控件位置,或者给他人使用等等的情况下就会非常麻烦,需要改动太多东西。
所以我们需要将整个控件封装起来,把里面的东西放到控件内部,以降低“轮播图”和这个项目的耦合性,增加可复用性。详解请参考iOS回顾笔记(03)
我们可以从用户的角度去考虑如何使用
用户使用应该以简单为主,这样简单设置就能用最好。至于内部实现才是我们要关心的 XYBannerView *banner = [XYBannerView bannerView]; banner.imagesArr = @[@"img_00",@"img_01",@"img_02",@"img_03",@"img_04"]; banner.frame = CGRectMake(37.5, 100, 300, 150); [self.view addSubview:banner];
|
新建BannerView文件
@property (nonatomic, strong) NSArray *imagesArr; + (instancetype)bannerView;
|
- (void)setImagesArr:(NSArray *)imagesArr { _imagesArr = imagesArr; for (int i = 0 ; i < imagesArr.count ;i++) { NSString *imageName = [NSString stringWithFormat:@"img_0%d",i]; UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:imageName]]; imageView.frame = CGRectMake(i * imageView.frame.size.width, 0, imageView.frame.size.width, self.scrollView.frame.size.height); [self.scrollView addSubview:imageView]; } self.scrollView.contentSize = CGSizeMake(imagesArr.count * self.scrollView.frame.size.width, 0); self.scrollView.pagingEnabled = YES; self.pageControll.numberOfPages = imagesArr.count; self.pageControll.currentPage = 1; self.pageControll.currentPageIndicatorTintColor = [UIColor yellowColor]; self.pageControll.pageIndicatorTintColor = [UIColor grayColor]; }
|
- (void)scrollViewDidScroll:(UIScrollView *)scrollView { // 实时监听当前第几页,根据当前偏移量来 self.pageControll.currentPage = (int)(self.scrollView.contentOffset.x / self.scrollView.frame.size.width + 0.5); }
|
定时器:NSTimer 就是一个可以设置定时功能的系统组件。
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.5 repeats:YES block:^(NSTimer * _Nonnull timer) { [self nextPage]; }]; [self.timer fire];
|
[self nextPage]方法的具体实现
NSInteger page = self.pageControll.currentPage + 1; if (page == self.pageControll.numberOfPages) { page = 0; } [UIView animateWithDuration:0.5 animations:^{ CGPoint offsize = self.scrollView.contentOffset; offsize.x = page * self.scrollView.frame.size.width; self.scrollView.contentOffset = offsize; }];
|
到这里一个简单的广告轮播就做完了
这个小轮播图马马虎虎也可以用了,但是还存在着一些严重的问题,下面来解决问题。
存在的问题
性能问题
- 从设计逻辑来看,这个图片轮播是直接创建了与图片相等的ImageView来进行轮播的。
- 如果直接传100张图片,同时创建100个imageView是很浪费内存的,并且用户还不一定会看
线程问题
- 用户拖拽图片的时候会造成轮播当时停下来,手一松又会快速的轮播
- 用户处理其他时间的时候,如底部有文本框编辑文字,轮播图会停止轮播
问题的解决
#pragma mark - 重写set方法 - (void)setImagesArr:(NSArray *)imagesArr { _imagesArr = imagesArr; [self setupContent]; self.pageControll.numberOfPages = imagesArr.count; self.pageControll.currentPage = 0; [self startTimer]; } - (void)setupContent { for (int i = 0; i < self.scrollView.subviews.count; i++) { UIImageView *imageView = self.scrollView.subviews[i]; NSInteger index = self.pageControll.currentPage; if (i == 0) { index--; } else if (i == 2) { index++; } if (index < 0) { index = self.pageControll.numberOfPages - 1; } else if (index >= self.pageControll.numberOfPages) { index = 0; } imageView.tag = index; imageView.image = [UIImage imageNamed:self.imagesArr[index]]; } self.scrollView.contentOffset = CGPointMake(self.scrollView.frame.size.width, 0); } #pragma mark - 代理监听页面滚动 - (void)scrollViewDidScroll:(UIScrollView *)scrollView { NSInteger page = 0; CGFloat minDistance = MAXFLOAT; for (int i = 0; i<self.scrollView.subviews.count; i++) { UIImageView *imageView = self.scrollView.subviews[i]; CGFloat distance = 0; distance = ABS(imageView.frame.origin.x - scrollView.contentOffset.x); if (distance < minDistance) { minDistance = distance; page = imageView.tag; } } self.pageControll.currentPage = page; } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { [self setupContent]; } - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { [self setupContent]; }
|
- 线程问题
- 这个原因是:NSTimer 默认是放到系统的主线程的,当用户操作其他主线程任务时,会造成NSTimer的线程阻塞,用户停止其他操作时又会重启NSTimer
- (void)startTimer { self.timer = [NSTimer scheduledTimerWithTimeInterval:1.5 repeats:YES block:^(NSTimer * _Nonnull timer) { [self nextPage]; }]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { [self endTimer]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [self startTimer]; }
|
到此完美的解决了上述两个问题,如果项目需求不是很复杂,这个已经完全够用了。
小结
广告轮播的Demo基本做完了,并且修复和完善了性能和体验上的问题。
简单项目中已经基本可用了。具体使用方法
导入框架后: XYBannerView *banner = [XYBannerView bannerView]; banner.imagesArr = @[@"img_00",@"img_01",@"img_02",@"img_03",@"img_04"]; banner.frame = CGRectMake(37.5, 100, 300, 150); [self.view addSubview:banner];
|
这个小框架还是很薄弱。并没有开放足够的接口给用户使用,这些也是后面需要一点点完善的,如果有兴趣,可以把你的意见和建议告诉我,这个小框架我会逐步完善。如果喜欢欢迎去我的github上下载源码,一起交流。