大概的实现思路有这么几种:
1 . UIScrollView + UIImageView1.1. 使用比 图片数 多 2 个的 UIImageView1.2 使用三个UIImageView 实现
1.3 使用 两个 UIImageView实现
2.UICollectionView (当然UICollectionView也是继承自UIScrollView)
2.1 使用UICollectionView 的复用特性,复制 多份 图片作为数据源,从中间开始显示
2.2 在 第一张图片前加 上最后一张图片,在最后一张图片前加上第一张图片
注意:
1.记得设置 scrollView.isPagingEnabled = true2. 控制 UIScrollView 回滚的时候 不要使用动画,否则会调用代理方法

1.1 使用比 图片数 多 2 个的 UIImageView 实现

思路 : 创建 比图片数多 2个UIImageView ,最一个ImageView显示第一张图片,第一个ImageView显示最后一张图片,从第二个到倒数第二个ImageView依次显示第一张到最后一张图片 当滚动到最后一张图片, 即 :原始 图片顺序为 1,2,3,4,5,6 ,则八个ImageView 显示图片的顺序为6,1,2,3,4,5,6,1,依次把ImageView添加到UIScrollView上,最开始显现第二个ImageView,当滚动到第一个 ImageView的时候,控制UIScrollView滚动到倒数第二个 ImageView,滚动到最后一个 ImageView的时候,控制UIScrollView滚到第二个ImageView ,只有 中间的 是正式用来显示的.两头的用来实现一个视差效果
核心代码如下

private lazy var mainScrollView : UIScrollView = {
        let scrollView = UIScrollView(frame: bounds)
        let scrollVWidth = bounds.width * CGFloat(images.count + 2)
        scrollView.contentSize = CGSize(width: scrollVWidth, height: bounds.height)
        scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        scrollView.delegate = self
        scrollView.bounces = true
        scrollView.isPagingEnabled = true
        for (index, imageV) in imageVs.enumerated() {
            imageV.frame =  CGRect(x: bounds.width  * CGFloat(index), y: 0, width: bounds.width, height: bounds.height)
            scrollView.addSubview(imageV)
        }
        return scrollView
    }()
// images 为根据传 过来的图片再在头尾分别加上最后一张和第一张图片创建的新数组
 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if scrollView.contentOffset.x >= (bounds.width  *  CGFloat(images.count - 1)) {
            mainScrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        }
        if scrollView.contentOffset.x <= 0 {
            mainScrollView.setContentOffset(CGPoint(x: bounds.width * CGFloat(images.count - 2), y: 0), animated: false)
        }
    }
1.2 使用三个UIImageView 实现

思路: 方法1.1 的进化版,方法1.1 在有大量图片的情况下,需要创建大量的ImageView 明显不合适,所以我们 把中间的一个ImageView片来代替 1.1中的 大量中间图片,即只有中间一张图片是用来正式显示图片的,每次滚动结束都需要滚回到 中间图片位置,两边的ImageView 用来实现视差效果
核心代码如下

// 这里为每个 ImageView 添加一个 tag ,用来标识当前显示的图片和后面计算将要显示的图片
  private lazy  var  leftImageV :  UIImageView = {
        let imageV = UIImageView(frame: bounds)
        imageV.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
        imageV.tag = images.count - 1
        imageV.image = images[images.count - 1]
        return imageV
    }()
    private lazy  var  MidleImageV :  UIImageView = {
        let imageV = UIImageView(frame: bounds)
         imageV.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
          imageV.tag = 0
          imageV.image = images[0]
        return imageV
    }()
    private lazy  var  RightImageV :  UIImageView = {
        let imageV = UIImageView(frame: bounds)
         imageV.frame = CGRect(x: bounds.width * 2, y: 0, width: bounds.width, height: bounds.height)
          imageV.tag = 1
          imageV.image = images[1]
        return imageV
    }()
    private lazy var imageVs : Array<UIImageView> = {
        return [leftImageV,RightImageV,MidleImageV];
    }()
    
    private lazy var mainScrollView : UIScrollView = {
        let scrollView = UIScrollView(frame: bounds)
        let scrollVWidth = images.count >= 3 ? bounds.width * 3 : bounds.width * CGFloat(images.count)
        scrollView.contentSize = CGSize(width: scrollVWidth, height: bounds.height)
        scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        scrollView.delegate = self
        scrollView.bounces = true
        scrollView.isPagingEnabled = true
        scrollView.addSubview(leftImageV)
        scrollView.addSubview(MidleImageV)
        scrollView.addSubview(RightImageV)
        return scrollView
    }()

 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        //判断 滚动方向,右滚下标 +1 ,左滚下标 -1
        var  direction  : Int = 0
        if scrollView.contentOffset.x > bounds.width {
            direction = 1
        }else if  scrollView.contentOffset.x < bounds.width{
            direction = -1
        }
      let imageVs = [leftImageV,MidleImageV,RightImageV]
        // 根据图片tag 和滚动方向 计算 要显示的 图片,更新 tag 值
        for  imageV in  imageVs {
            var  index  = imageV.tag + direction
            if index == images.count {
                index = 0
            }else if index == -1{
                index = images.count - 1
            }
            imageV.image = images[index]
            imageV.tag = index
        }
        mainScrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
    }
1.3 使用 两个 UIImageView实现

思路: UIScrollViewcontentSize 依然 为 三倍 屏幕宽度,创建两个 ImageView 添加到 UIScrollView 上, 没有滚动时,两个imageView重叠,上面的ImageView显示当前图片,发生滚动的时候,判断滚动方向,把底下的ImageView移到 左边或者右边,滚动结束,UIScrollView 滚动 回 中间位置
核心代码如下:

private  var isChange : Bool = true
    let images : Array<UIImage>
    lazy var  bottomImageView : UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "2.png"))
        imageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
        imageView.tag = 0
        return imageView
    }()
    
    lazy var  topImageView : UIImageView = {
        let imageView = UIImageView(image: UIImage(named: "1.png"))
        imageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
         imageView.tag = 0
        return imageView
    }()
    
    lazy var  scrollView : UIScrollView = {
        let scroView = UIScrollView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height))
        scroView.contentSize = CGSize(width: bounds.width * 3, height: bounds.height)
        scroView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        scroView.isPagingEnabled = true
        scroView.bounces = false
        scroView.delegate = self
        scroView.addSubview(bottomImageView)
        scroView.addSubview(topImageView)
        return scroView
    }()
 
    func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
         imageOffset = scrollView.contentOffset.x
    }
   // 每次滚动的时候 判断 滚动方向,调整底下的 imageview  的位置和显示的图片
   
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if isChange{
        if scrollView.contentOffset.x < bounds.width {
            imageOffset =  scrollView.contentOffset.x
            var tag = bottomImageView.tag - 1
            if tag < 0{
                tag = images.count - 1
            }
            bottomImageView.image = images[tag]
            bottomImageView.frame = CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height)
            bottomImageView.tag = tag
        }else if  scrollView.contentOffset.x > bounds.width {
            imageOffset = scrollView.contentOffset.x
            var tag = bottomImageView.tag + 1
            if tag  == images.count{
                tag = 0
            }
            bottomImageView.image = images[tag]
            bottomImageView.frame = CGRect(x: bounds.width * 2, y: 0, width: bounds.width, height: bounds.height)
            bottomImageView.tag = tag
        }
        }
        isChange = false
    }
    
   //滚动结束, scrollView 回滚到中间位置,更新 上面的 ImageView 的图片
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
                bottomImageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
                topImageView.frame = CGRect(x: bounds.width, y: 0, width: bounds.width, height: bounds.height)
                print(bottomImageView.tag)
                topImageView.image = images[bottomImageView.tag]
        scrollView.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        isChange = true
    }

这里 因为 scrollViewDidScroll 代理滚动过程中会一直调用,而我这里只需要判断 一下滚动方向然后调整 底下的ImageView, 所以用的 一个BOOL值来限制执行,当时没想到一个比较好的方案,所以实现看起来比较low

2.1 使用UICollectionView 的复用特性,复制 多份 图片作为数据源,从中间开始显示

思路: 复制 多份 需要显示的图片 作为UICollectionView数据源,开始的时候 就把scrollView 滚动到中间那份 数据的开头位置,因为复制的多份数据,所以滚动的时候就会无限轮播的效果,这里就不贴具体代码,大家可以自行 尝试下

2.2 在 第一张图片前加 上最后一张图片,在最后一张图片前加上第一张图片

思路:1.1 方法类似,只是使用 UICollectionView 的话因为UICollectionView自身的复用机制,所以没有 1.1方法 创建大量 ImageView的性能问题,同样的,在首尾 分别加上最后和第一张图片,当 滚动到这两个位置的时候,控制 scrollView滚动到中间显示区的对应位置
核心代码如下:

let images : Array<UIImage>
//创建  UICollectionView 
    lazy var collectionV : UICollectionView = {
        let layout  = UICollectionViewFlowLayout()
        layout.minimumInteritemSpacing = 0
        layout.minimumLineSpacing = 0
        layout.itemSize = CGSize(width: bounds.width, height: bounds.height)
        layout.scrollDirection = .horizontal
        let collectionV  = UICollectionView(frame: CGRect(x: 0, y: 0, width: bounds.width, height: bounds.height), collectionViewLayout: layout)
        collectionV.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
        collectionV.dataSource = self
        collectionV.delegate = self
        collectionV.isPagingEnabled = true
        return collectionV
    }()
    //根据传进来的 图片数组构建 数据源数组
    init(frame: CGRect,images :Array<UIImage> ) {
        var tempImages : Array<UIImage> = [images[images.count - 1]]
        for  image in images {
            tempImages.append(image)
        }
        tempImages.append(images[0])
        self.images = tempImages
        super.init(frame: frame)
        addSubview(collectionV)
        collectionV.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
    }

extension XlXBannerCollectionV : 
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        cell.imageV = UIImageView(image: images[indexPath.row])
        return cell
    }
    
// 滚动到 首尾的时候控制回滚
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        if scrollView.contentOffset.x >= (bounds.width  *  CGFloat(images.count - 1)) {
            collectionV.setContentOffset(CGPoint(x: bounds.width, y: 0), animated: false)
        }
        if scrollView.contentOffset.x <= 0 {
             collectionV.setContentOffset(CGPoint(x: bounds.width * CGFloat(images.count - 2), y: 0), animated: false)
        }
    }
}

无限轮播 整体 上就是 中间部分 是用来正式显示的,首尾的是 在滚动的时候让用户有种 后面还有内容的错觉,当滚动停止,立马回滚到中间显示区的对应位置,来达到无限轮播的视觉效果

自动轮播的话在 scrollView 代理方法里面合适的位置开启或暂停定时器来实现,代码中剔除了其他干扰只保留了核心部分,因为功能比较简单,所以也没注意代码卫生,实际开发中,各位小伙伴们还是要多多注意下代码习惯,抽取封装,配置工具类什么的来实现的漂亮一点

最后:写完了才想起来两张图片的情况下没做处理,可能还是会有些问题吧,道理都是一样的,实际开发中多多注意下