iOS-轮播图(自动和循环)本地图片

在另一篇文章有处理网络图片的方法
可以在GitHub上直接下载文件
https://github.com/peiDuo/PDBannerView
声明文件部分 只需要调用initWithFrame方法传入本地的图片数组就可以了

//
//  BannerView.h
//  轮播图(循环和自动)
//
//  Created by 裴铎 on 2018/3/9.
//  Copyright © 2018年 裴铎. All rights reserved.
//

#import 


/**
 7./创建一个协议
 需要把文件的名称传过来
 用@class BannerView;只传名称
 */
@class BannerView;

//定义一个协议( @protocol 协议名称  )
@protocol BannerViewDelegate 

/**
 协议方法实现方式
 optional 可选 (默认是必须实现的)
 */
@optional

/**
 8./用来传值的协议方法

 @param bannerView 本类
 @param currentImage 传进来的当前的图片
 */
- (void)selectImage:(BannerView *)bannerView currentImage:(NSInteger)currentImage;

@end//协议结束

@interface BannerView : UIView

/**
 9./创建一个代理属性(用 weak)
 写在.h文件里的属性 外界可以调用
 */
@property (nonatomic , weak) id  delegate;

/**
 销毁定时器方法
 */
+ (void)destroyTimer;

/**
 1./声明一个自定义的构造方法 让外界的对象用来初始化bannerView

 @param frame 外界传入的frame
 @param addImageArray 外界传入的图片数组
 @return 1
 */
- (id)initWithFrame:(CGRect)frame andImageArray:(NSMutableArray *)addImageArray;

@end

实现文件部分

//
//  BannerView.m
//  轮播图(循环和自动)
//
//  Created by 裴铎 on 2018/3/9.
//  Copyright © 2018年 裴铎. All rights reserved.
//

#import "BannerView.h"

/**
 定时器 用来自动播放图片
 */
static NSTimer * mv_timer;

/**
 定义私有变量
 遵守滚动式图的代理方法 实现拖拽效果
 */
@interface BannerView () <
UIScrollViewDelegate>{
    
    //banNerView的宽和高 私有成员变量用下划线开头(书写习惯)
    CGFloat mv_width;
    CGFloat mv_height;
}

/**
 分页控件
 */
@property (nonatomic , strong) UIPageControl * mainPage;

/**
 scrollView
 */
@property (nonatomic , strong) UIScrollView * mainScrollView;

/**
 图片数组
 */
@property (nonatomic , strong) NSMutableArray * dataArray;

@end

@implementation BannerView

//- (instancetype)initWithFrame:(CGRect)frame{
//
//    self = [super initWithFrame:frame];
//
//    if (self) {
//
//       //系统的初始化方法
//    }
//
//    return self;
//}


/**
 2./自定义的init构造方法 在.h文件提前声明

 @param frame 外界初始化时传入的frame 带有bannerView的宽和高
 @param addImageArray 传入的图片数组
 @return 1
 */
- (id)initWithFrame:(CGRect)frame andImageArray:(NSMutableArray *)addImageArray{
    
    //调用父类方法
    self = [super initWithFrame:frame];
    
    //判断是否是本类对象调用 并 外界传入的图片数量足够滚动
    if (self && addImageArray.count > 2) {
        
        /**
         获取banNerView 的宽度
         宽和高是外界传入的 frame (只能在这个方法内有效)
         所以需要定义一个本类都能使用的成员变量
         */
        mv_width = frame.size.width;
        
        //或取bannerView 的高度
        mv_height = frame.size.height;
        
        //图片数组 1 2 3 4 5 6 把外界传入的图片数组赋值给本类的数组
        self.dataArray = [NSMutableArray arrayWithArray:addImageArray];
        
        //在数组的最后一位添加传进来的第一张图片 1 2 3 4 5 6 1
        [self.dataArray addObject:addImageArray.firstObject];
        
        /**
         在数组的第一位添加传进来的最后一张图片 6 1 2 3 4 5 6 1
         insert 插入元素  atIndex: 根据下标
         */
        [self.dataArray insertObject:addImageArray.lastObject atIndex:0];
        
        //初始化时把mainscrollView 加载到banNerView上
        [self addSubview:self.mainScrollView];
        
        //初始化时把分页控件加载到bannerView中
        [self addSubview:self.mainPage];
        
        //初始化时加载定时器
        [self addTimer];
    }
    
    /**
     返回本类
     当外界用本类的初始化方法时
     返回一个视图 bannerView 给外界
     */
    return self;
}

/**
 滚动视图开始手动拖拽时出发
 */
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    
    //判断是否有定时器
    if (mv_timer) {
        
        //如果有定时器就暂停定时器(两种方法实现暂停效果)
        
        /**
         NSTimer 自带的方法中没有暂停和继续定时器的方法
         但是有一个setFireDate:方法 (定时器的触发时间)
         原理是把定时器的触发时间设置成很久的将来
         这样定时器就会进入等待触发的状态 (实现暂停效果)
         distantFuture(遥远的未来)
         */
        [mv_timer setFireDate:[NSDate distantFuture]];
        
        /**
         用NSTimer自带的停止定时器的方法 invalidate
         这个方法会吧定时器永久停止,无法再次启用
         所以需要把定时器清空 nil
         当需要再次开启定时器时 重新初始化定时器
         [self.timer invalidate];
         self.timer = nil;
         */
    }
}

/**
 滚动视图正在滚动 (拖拽过程中触发的方法)
 */
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    
    self.mainPage.currentPage = scrollView.contentOffset.x / mv_width - 1;
}

/**
 滚动视图完成减速时调用 (就是手动拖拽完成后)
 */
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    
    //判断是否有定时器
    if (mv_timer) {
        
        /**
         设置定时器的触发时间
         延后2秒触发
         */
        [mv_timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
        
        /**
         重新初始化定时器
         self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerFUNC:) userInfo:nil repeats:YES];
         */
    }
    
    //获取当前滚动视图的偏移量
    CGPoint currentPoint = scrollView.contentOffset;
    
    /** 判断拖拽完成后将要显示的图片时第几张 6 1 2 3 4 5 6 1 */
    //如果是数组内的最后一张图片 1
    if (currentPoint.x == (self.dataArray.count - 1) * mv_width) {
        
        //改变偏移量 显示数组内的第一张图片 1
        scrollView.contentOffset = CGPointMake(mv_width, 0);
    }
    
    //如果是数组内的第一张图片 6
    if (currentPoint.x == 0) {
        
        //改变偏移量 显示数组内的 第二个图片6
        scrollView.contentOffset = CGPointMake((self.dataArray.count - 2) * mv_width, 0);
    }
    
    /**
     如果是图片数组的第一张图片 或 最后一张图片时
     滚动视图的偏移量发生了改变
     所以之前的偏移量变量不能再使用了 (获取一个新的偏移量)
     */
    //获取新的滚佛那个视图偏移量
    CGPoint newPoint = scrollView.contentOffset;
    
    //改变分页控件上的页码
    self.mainPage.currentPage = newPoint.x / mv_width - 1;
}

/**
 5./初始化定时器
 */
- (void)addTimer{
    
    //初始化定时器 时间戳:2.0秒 目标:本类 方法选择器:timerFUNC 用户信息:nil 是否循环:yes
    mv_timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timerFUNC:) userInfo:nil repeats:YES];
    
    /**
     将定时器添加到当前线程中(currentRunLoop 当前线程)
     [NSRunLoop currentRunLoop]可以的到一个当前线程下的NSRunLoop对象
     addTimer:添加一个定时器
     forMode:什么模式
     NSRunLoopCommonModes 共同模式
     */
    [[NSRunLoop currentRunLoop] addTimer:mv_timer forMode:NSRunLoopCommonModes];
    /**
     在开启一个NSTimer实质上是在当前的runloop中注册了一个新的事件源,
     而当scrollView滚动的时候,当前的MainRunLoop是处于UITrackingRunLoopMode的模式下,
     在这个模式下,是不会处理NSDefaultRunLoopMode的消息(因为RunLoop 的 Mode不一样),
     要想在scrollView滚动的同时也接受其它runloop的消息,我们需要改变两者之间的runLoopMode.
     简单的说就是NSTimer不会开启新的进程,只是在RunLoop里注册了一下,
     RunLoop每次loop时都会检测这个timer,看是否可以触发。
     当Runloop在A mode,而timer注册在B mode时就无法去检测这个timer,
     所以需要把NSTimer也注册到A mode,这样就可以被检测到。
     所以模式参数 forMode: 填写 NSRunLoopCommonModes 共同模式
     */
}

/**
 6./实现定时器方法
 */
- (void)timerFUNC:(NSTimer *)timer{
    
    /**
     获取当前图片的X位置
     也就是定时器再次出发时滚动视图上正在显示的是哪一张图片
     */
    CGFloat currentX = self.mainScrollView.contentOffset.x;
    
    /**
     获取下一张图片的X位置
     当前位置 + 一个屏幕宽度
     */
    CGFloat nextX = currentX + mv_width;
    
    /**
     判断滚动视图上将要显示的图片是最后一张时
     通过X值来判断 所以要 self.dataArray.count - 1
     */
    if (nextX == (self.dataArray.count - 1) * mv_width) {
        
        /**
         UIView的动画效果方法(分两个方法)
         */
        [UIView animateWithDuration:0.2 animations:^{
            /**
             动画效果的第一个方法
             Duration:持续时间
             animations:动画内容
             这个动画执行 0.2秒 后进入下一个方法
             */
            
            //往最后一张图片走
            self.mainScrollView.contentOffset = CGPointMake(nextX, 0);
            
            /**
             改变对应的分页控件显示圆点
             */
            self.mainPage.currentPage = 0;
        } completion:^(BOOL finished) {
            /**
             动画效果的第二个方法
             completion: 回调方法 (完成\结束的意思)
             上一个方法结束后进入这个方法
             */
            
            //往第二张图片走
            self.mainScrollView.contentOffset = CGPointMake(self->mv_width, 0);
        }];
    }else{//如果滚动视图上要显示的图片不是最后一张时
        
        //显示下一张图片
        [UIView animateWithDuration:0.2 animations:^{
            
            //让下一个图片显示出来
            self.mainScrollView.contentOffset = CGPointMake( nextX, 0);
            
            //改变对应的分页控件显示圆点
            self.mainPage.currentPage = self.mainScrollView.contentOffset.x / self->mv_width - 1;
        } completion:^(BOOL finished) {
            
            //改变对应的分页控件显示圆点
            self.mainPage.currentPage = self.mainScrollView.contentOffset.x / self->mv_width - 1;
        }];
    }
}

/**
 4./加载分页控件
 */
- (UIPageControl *)mainPage{
    
    if (!_mainPage) {
        
        //初始化分页控制器
        _mainPage = [[UIPageControl alloc]initWithFrame:CGRectMake( 50, mv_height - 20, mv_width - 50 * 2, 20)];
        
        //分页控件上要显示的圆点数量
        _mainPage.numberOfPages = self.dataArray.count - 2;
        //分页控件不允许和用户交互(不许点击)
        _mainPage.userInteractionEnabled = NO;
        
        //设置 默认点 的颜色
        _mainPage.pageIndicatorTintColor = [UIColor whiteColor];
        
        //设置 滑动点(当前点) 的颜色
        _mainPage.currentPageIndicatorTintColor = [UIColor greenColor ];
    }
    
    return _mainPage;
}

/**
 3./加载滚动视图
 */
- (UIScrollView *)mainScrollView{
    
    if (!_mainScrollView) {
        
        //初始化滚动控件
        _mainScrollView = [[UIScrollView alloc]initWithFrame:CGRectMake(0, 0, mv_width, mv_height)];
        
        //滚动式图的代理
        _mainScrollView.delegate = self;
        
        /**
         滚动范围(手动拖拽时的范围)
         如果不写就不能手动拖拽(但是定时器可以让图片滚动)
         */
        _mainScrollView.contentSize = CGSizeMake(self.dataArray.count * mv_width, mv_height);
        
        //分页滚动效果 yes
        _mainScrollView.pagingEnabled = YES;
        
        //能否滚动
        _mainScrollView.scrollEnabled = YES;
        
        //弹簧效果 NO
        _mainScrollView.bounces = NO;
        
        //滚动视图的起始偏移量
        _mainScrollView.contentOffset = CGPointMake(mv_width, 0);
        
        //垂直滚动条
        _mainScrollView.showsVerticalScrollIndicator = NO;
        
        //水平滚动条
        _mainScrollView.showsHorizontalScrollIndicator = NO;
        
        /**
         循环往滚动视图上添加图片视图
         循环条件 i < self.dataArray.count 一定不要写等号 =
         如果 i <= self.dataArray.count 程序就会崩溃,(下标越界)
         */
        for (int i = 0; i < self.dataArray.count; i ++) {
            
            //初始化图片视图
            UIImageView * imgV = [[UIImageView alloc]initWithFrame:CGRectMake(mv_width * i, 0, mv_width, mv_height)];
            
            //给图片视图添加图片 通过图片数组
            imgV.image = [UIImage imageNamed:self.dataArray[i]];
            
            //让图片可以与用户交互
            imgV.userInteractionEnabled = YES;
            
            //初始化一个点击手势
            UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapAcyion:)];
            
            //把点击手势添加到图片上
            [imgV addGestureRecognizer:tap];
            
            //把图片视图 添加 到滚动视图上
            [_mainScrollView addSubview:imgV];
        }
    }
    
    //返回滚动视图 给 bannerView
    return _mainScrollView;
}
/**
 点击图片触发的手势方法
 */
- (void)tapAcyion:(UITapGestureRecognizer *)tap{
    
    /**
     如果代理属性能够响应协议方法方法
     才会通过代理属性 调用协议方法
     */
    if ([self.delegate respondsToSelector:@selector(selectImage:currentImage:)]) {
        
        /**
         通过代理属性 调用协议方法
         currentImage:当前的图片时第几张
         可以通过分页控件的当前圆点来判断是第几张图片
         */
        [self.delegate selectImage:self currentImage:self.mainPage.currentPage];
    }
}

+ (void)destroyTimer{
    //  清理定时器
    [mv_timer invalidate];
    mv_timer = nil;
}


@end

你可能感兴趣的:(iOS-轮播图(自动和循环)本地图片)