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