Github:https://github.com/lxypeter/CYCircularScrollView
轮播图控件实现很多,基本都思路有两种:1、使用UIScrollView + 2-3 个UIImageView的复位使用;2、使用UICollectionView。虽然网上已经有不少优秀的实现,但适逢开始学习Swift,而且这种旋转木马式的循环展现也有抽离的封装的价值,于是自己着手实现了基于UICollectionView的轮播图控件CYPicBannerScrollView及支持自定义视图的循环控件CYCircularScrollView。先上效果图:
安装
CocoaPods
pod 'CYCircularScrollView'
CocoaPods版本需要在1.1.0以上
手动引入
你也可以手动将CYCircularScrollView拷贝到项目中,但注意项目中需要同时引入Kingfisher,因为在轮播图控件** CYPicBannerScrollView**中直接使用了Kingfisher框架进行网络图片加载。
如何使用
轮播图控件:CYPicBannerScrollView
1. 初始化
CYPicBannerScrollView支持三种类型的数据源:UIImage
、图片链接字符串
以及数据模型(Model)
,与之对应的有3种初始化方法。
- UIImage类型
convenience init(frame:CGRect, images:[Any]?, didSelected:((Int,Any)->())?)
//e.g.
let imageArray = [UIImage(named: "banner_1")!,UIImage(named: "banner_2")!,UIImage(named: "banner_3")!]
let bannerView = CYPicBannerScrollView(frame: CGRect.zero,
images:imageArray ) { (index, data) in
//click event
}
- 图片链接字符串
convenience init(frame:CGRect, urlStrings:[Any]?, placeholder:UIImage?, didSelected:((Int,Any)->())?)
//e.g.
let urlArray = ["www","www","www"]
let bannerView = CYPicBannerScrollView(frame: CGRect.zero,
urlStrings: urlArray,
placeholder: UIImage(named: "pic_placeholder")) { (index, data) in
//click event
}
- 数据模型(Model)
convenience init(frame:CGRect, models:[Any]?, placeholder:UIImage?, modelImage:@escaping (Any)->(CYImageResult), didSelected:((Int,Any)->())?)
//e.g.
let announceArray = [Announcement(title:"First Announcement",time:"2017-01-01",image:"p1"),
Announcement(title:"Second Announcement",time:"2017-01-02",image:"p2"),
Announcement(title:"Third Announcement",time:"2017-01-03",image:"p3")]
let bannerView = CYPicBannerScrollView(frame: CGRect.zero,
models: announceArray,
placeholder: UIImage(named: "pic_placeholder"),
modelImage: { (model) -> (CYImageResult) in
let announcement = model as! Announcement
return CYImageResult(data: UIImage(named: announcement.image)!, type: .image)
}) { (index, data) in
//click event
}
对于对象模型类型的初始化,需要在modelImage
闭包中根据数据模型返回图片数据源,支持直接返回UIImage如:CYImageResult(data: UIImage(named: model.image)!, type: .image)
或链接字符串如:CYImageResult(data: "http://image.com/image", type: .urlString)
2. 自定义样式
CYPicBannerScrollView支持以下形式的客制化
bannerView.autoScrollInterval = 3.0 //自动翻页间隔,默认为5秒
bannerView.pageControlPosition = .right //PageControl的位置,默认为center
bannerView.pageControlOffset = UIOffset(horizontal: -10, vertical: -5) //PageControl的基于位置的偏移量
//自定义PageControl的样式
bannerView.pageControl.backgroundColor = UIColor(displayP3Red: 51/255.0, green: 51/255.0, blue: 51/255.0, alpha: 0.8)
bannerView.pageControl.layer.cornerRadius = 8
支持自定义视图的循环控件:CYCircularScrollView
要实现自定义视图循环展示只需要四步
- 1.继承
CYCircularScrollView
class CYAnnounceScrollView : CYCircularScrollView
- 2.覆写
var cellClass:UICollectionViewCell.Type
属性
override var cellClass:UICollectionViewCell.Type {
return CYAnnounceCell.self //循环的视图类型,需为UICollectionViewCell子类
}
- 3.覆写
func configureCollectionCell(_ cell:UICollectionViewCell, data:Any) -> UICollectionViewCell
方法
override func configureCollectionCell(_ cell:UICollectionViewCell, data:Any) -> UICollectionViewCell {
let announceCell = cell as! CYAnnounceCell
if let announcement:Announcement = data as? Announcement{
announceCell.announcement = announcement
}
return announceCell
}
- 4.根据需求覆写属性
override var scrollDirection:CYScrollDirection {
return .vertical//滚动方向,默认为.horizontal
}
override var isScrollEnabled:Bool {
return false//是否可以手动滚动,默认为true
}
此时你就能使用你自定义的视图控件了~
let annouceScrollView = CYAnnounceScrollView(frame: CGRect.zero, datas: self.announceArray) { (index, data) in
//click event
}
更详尽的使用可以参照Github上的Demo。
实现思路
CYCircularScrollView是以UICollectionView
为基础的,借助UICollectionView
的可重用特性,让滚动的效果更加流畅,实现也更为简单。而为了能够实现无限循环滚动,在数据源上作了如下处理:
当实际的数据源容量大于1时,
UICollectionView
接收的数据量加2,在前后增添各一缓冲视图,用以优化到达数据源边界时的滚动效果。同时监听UICollectionView
代理下的滚动事件public func scrollViewDidScroll(_ scrollView: UIScrollView)
。
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if self.dataArray == nil {
return
}
var index:Float
switch self.scrollDirection {
case .horizontal:
index = Float(scrollView.contentOffset.x * 1.0 / scrollView.frame.size.width)
case .vertical:
index = Float(scrollView.contentOffset.y * 1.0 / scrollView.frame.size.height)
}
if index < 0.25 {
self.collectionView.scrollToItem(at: IndexPath(item: self.dataArray!.count, section: 0), at: [.top,.left], animated: false)
}else if index >= Float(self.dataArray!.count+1) {
self.collectionView.scrollToItem(at: IndexPath(item: 1, section: 0), at: [.top,.left], animated: false)
}
let page = self.transferIndex(Int(index))
scrollToPage(page)
}
当UICollectionView
滚动至首项(实际数据源末项的缓冲视图)及尾项(实际数据源首项的缓冲视图)并即将展示完时,UICollectionView
立即以无动画效果跳转至缓冲视图对应的实际项所在,以尽可能少的掉帧达到无限循环的效果。而当前页数的监听与刷新也在该步完成,避免了当存在UIPageControl
等分页控件时当前页数的延后刷新。相比于重复复制数据源而达到无限循环效果的实现,这种方法一方面节省了内存空间,同时也避免了因为某些意外到达边界时的断层甚至崩溃。
以上。