iOS-UIScrollView无限轮播

四月 25, 2016

继续阅读

大家在美团、点评等众多商业软件肯定见过广告轮播,一般情况下,为了显得自然都会使用从最后一张向后滑动至第一张,感觉就像一个环一样。然而UIScrollView是不可能做成一个环的,那该怎么办呢?当然需要我们耍点小聪明了。

这个小聪明怎么刷呢?其实很简单。首先我们需要有一个数组存储UIImage或图片路径,然后我们只需要把UIScrollView实例分成3页,依次为previousPage、currentPage、lastPage,然后为为其添加一个currentNo属性记录currentPage当前指向的图片在数组中的索引。当向后滑动后,显示lastPage页,然后直接将currentPage也设置为lastPage的图片,然后再偷梁换柱将contentOffset设置为currentPage页的offset,此时窗口显示的就又成为currentPage中的内容了,而且这一变化你只要不加动画用户是感受不到的。然后将currentNo加1,但是在这里要考虑加1之前是不是最后一张,如果是,加1后为0。然后重新设置previousPage的图片为currentNo-1,设置lastPage的图片为currentNo+1即可。之后再重复上述内容就可以实现。

下面直接放代码,个人觉得注释写的也很详细,大家认真看应该都会懂。顺便推销一下,这个轮播已经发布到GitHub了,项目地址:https://github.com/terrynie/TNScrollView

TNScrollView.h

#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, TNScrollViewDirection) {  
    TNScrollViewDirectionVertical,   //scrollView scroll on vertical
    TNScrollViewDirectionHorizontal  //scrollView scroll on horizontal
};

@interface TNScrollView : UIView
//the images in ScrollView
@property(nonatomic, retain)               NSArray *images;
@property(nonatomic, assign) TNScrollViewDirection dirction;      //the scrollView scroll direction
@property(nonatomic, assign)               CGFloat timeInterval;  //please set timeInterval >0 ; scrollview change pages automatically after every timeInterval. 0 is not change automatically .
@property(nonatomic, assign) BOOL autoShow;                       //auto scroll or not

+(instancetype)scrollViewWithFrame:(CGRect)frame andDirection:(TNScrollViewDirection)direction;
-(instancetype)initWithFrame:(CGRect)frame andDirection:(TNScrollViewDirection)direction;
@end

TNScrollView.m

 #import "TNScrollView.h"

 #pragma mark - extension

 @interface TNScrollView () <UIScrollViewDelegate>
 @property (weak, nonatomic)   IBOutlet    UIScrollView   *scrollView ;
 @property (weak, nonatomic)   IBOutlet    UIPageControl  *pageControl;
 @property (strong, nonatomic) UIImageView *previousImage;
 @property (strong, nonatomic) UIImageView *currentImage;
 @property (strong, nonatomic) UIImageView *lastImage;
 @property (retain, nonatomic) NSTimer     *timer;
 @property (assign, nonatomic) NSInteger   currentImageNo;

 @property(assign, nonatomic)  CGFloat     width   ;
 @property(assign, nonatomic)  CGFloat     height  ;
 @property(assign, nonatomic)  CGFloat     offsetX ;
 @property(assign, nonatomic)  CGFloat     offsetY ;
 @property(assign, nonatomic)  NSInteger   count   ;
 @end

 @implementation TNScrollView

 #pragma mark - init / calss factory method
 +(instancetype)scrollView {
     TNScrollView *sc = [[TNScrollView alloc] init];
     return sc;
 }

 +(instancetype)scrollViewWithFrame:(CGRect)frame andDirection:(TNScrollViewDirection)direction{
     TNScrollView *sc = [[TNScrollView alloc] initWithFrame:frame andDirection:direction];
     return sc;
 }

 -(instancetype)init {
     if (self = [super init]) {
         self = [[[NSBundle mainBundle] loadNibNamed:@"TNScrollView" owner:nil options:nil] lastObject];
         //set TNScrollView as scrollView's delegate
         self.scrollView.delegate = self;
     }
     return self;
 }

 -(instancetype)initWithFrame:(CGRect)frame {
     if (self = [super initWithFrame:frame]) {
         self = [[[NSBundle mainBundle] loadNibNamed:@"TNScrollView" owner:nil  options:nil] lastObject];
         self.frame = frame;
         self.scrollView.frame = self.bounds;
         self.scrollView.pagingEnabled = YES;
         //set TNScrollView as scrollView's delegate
         self.scrollView.delegate = self;
     }
     return self;
 }

/*!
 *  init with direction
 *  @return TNScrollView
 */
-(instancetype)initWithFrame:(CGRect)frame andDirection:(TNScrollViewDirection)direction {
    self = [self initWithFrame:frame];
    self.dirction = direction;
    return self;
}

#pragma mark - setMethod

-(void)setImages:(NSArray *)images {
    _images = images;
    _count = _images.count;

    //设置pageControl的页数
    self.pageControl.numberOfPages = _count;
    //imageView宽度
    _width   = self.scrollView.frame.size.width ;
    //imageView高度
    _height  = self.scrollView.frame.size.height;
    //设置默认首张图片编号为0
    self.currentImageNo = 0;

    CGFloat offsetX = _width,
    offsetY = _height;
    self.dirction   == TNScrollViewDirectionHorizontal ? (offsetY = 0) : (offsetX = 0);
    CGRect frame    =  CGRectMake(offsetX, offsetY, _width, _height);

    //判断图片总数
    if (_count == 1) {
        self.scrollView.contentOffset = CGPointMake(_width, 0);
        self.scrollView.scrollEnabled = NO;
    } else {
        //设置滚动范围
        if (self.dirction == TNScrollViewDirectionHorizontal) {
            [self.scrollView setContentSize:CGSizeMake(_width * 3, _height)];
            self.scrollView.contentOffset = CGPointMake(_width, 0);
        } else if (self.dirction == TNScrollViewDirectionVertical) {
            [self.scrollView setContentSize:CGSizeMake(_width, 3 * _height)];
            self.scrollView.contentOffset = CGPointMake(0, _height);
        }
        self.previousImage = [[UIImageView alloc] initWithFrame:CGRectMake(frame.origin.x-offsetX, frame.origin.y-offsetY, frame.size.width, frame.size.height)];
        self.lastImage     = [[UIImageView alloc] initWithFrame:CGRectMake(frame.origin.x+offsetX, frame.origin.y+offsetY, frame.size.width, frame.size.height)];
    }

    //设置第一张图片
    self.currentImage       = [[UIImageView alloc] initWithFrame:frame];
    self.currentImage.image = [UIImage imageNamed:self.images[0]];
    [self.scrollView addSubview:self.currentImage];
    self.pageControl.currentPage = self.currentImageNo;

    if (_count >= 2) {
        //初始化previousImage
        self.previousImage.image = [UIImage imageNamed:self.images[_count-1]];
        [self.scrollView addSubview:self.previousImage];
        //初始化lastImage
        if (_count == 2) {
            self.lastImage.image = [UIImage imageNamed:self.images[_count-1]];
        } else {
            self.lastImage.image = [UIImage imageNamed:self.images[1]];
        }
        [self.scrollView addSubview:self.lastImage];
    }
    _offsetX = self.currentImage.frame.origin.x ;
    _offsetY = self.currentImage.frame.origin.y ;
    //设置定时器
    if (self.timeInterval != 0) {
        [self addTimer];
    }
}

//设置时间间隔
-(void)setTimeInterval:(CGFloat)timeInterval {
    _timeInterval = timeInterval;
    if (timeInterval != 0) {
        [self addTimer];
    }
}

#pragma mark - <UIScrollViewDelegate>
//手指滑动结束
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    NSLog(@"%s---%f,%f",__func__,self.scrollView.contentOffset.x,self.scrollView.contentOffset.y);
    CGFloat scrollOffset; //滚动后的contentOffset
    CGFloat offset;       //如果布局为水平布局offset=_width,如果是垂直布局offset=_height
    self.dirction == TNScrollViewDirectionHorizontal ? (scrollOffset = self.scrollView.contentOffset.x, offset = _width) : (scrollOffset = self.scrollView.contentOffset.y, offset = _height);
    NSLog(@"%f,%f,%ld",scrollOffset,offset,self.currentImageNo);

    if (scrollOffset < (offset/2)) {
        if (self.currentImageNo == (0)) {
            self.currentImageNo = _count-1;
        }else {
            self.currentImageNo --;
        }

    }else if (scrollOffset > (offset/2)*3) {
        if (self.currentImageNo == (_count-1)) {
            self.currentImageNo = 0;
        }else {
            self.currentImageNo ++;
        }
    }

    //获取主队列,在主线程中更新UI,之前在这里被坑了。。。
    dispatch_async(dispatch_get_main_queue(), ^{
        [self fixImageView];
    });
    //重置定时器
    [self addTimer];
    NSLog(@"%ld",self.currentImageNo);
}

//手指开始滑动
-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    //使定时器失效
    [self.timer invalidate];
}


#pragma mark - tool methods

-(void)changeOffset {
    CGPoint contentOffset;
    CGFloat x = 0, y = 0;
    self.dirction == TNScrollViewDirectionVertical ? (y = _height) : (x = _width);
    //下一页的offset
    contentOffset = (CGPoint) {_offsetX + x, _offsetY + y};

    [UIView animateWithDuration:1.5 animations:^{
        self.scrollView.contentOffset = contentOffset;
    }];

    if (self.currentImageNo == (_count-1)) {
        self.currentImageNo = 0;
    }else {
        self.currentImageNo ++;
    }

    [self fixImageView];
}

//修正图片视图
-(void)fixImageView {
    //设置pagecontrol当前页是第几页
    self.pageControl.currentPage = self.currentImageNo;
    //重设scrollView中的各个image
    self.currentImage.image = [UIImage imageNamed:self.images[self.currentImageNo]];
    self.scrollView.contentOffset = (CGPoint){_offsetX, _offsetY};

    self.previousImage.image = [UIImage imageNamed:self.images[(self.currentImageNo-1+_count) % _count]];
    self.lastImage.image = [UIImage imageNamed:self.images[(self.currentImageNo+1) % _count]];
}

//添加计时器
-(void)addTimer {
    self.timer = [NSTimer scheduledTimerWithTimeInterval:self.timeInterval target:self selector:@selector(changeOffset) userInfo:nil repeats:YES];
}
@end

Terry