Swift 动画滚动的banner

"迈入Swift的学习中"

Posted by 李勇 on 2017-11-01

##原理概述
首先说一下整个banner工具的实现原理,所用控件主要是UICollectionView,页码UIPageControl,以及计时器Timer。根据要滚动的数据显示在三个section里面,每个section都展示所有的数据,默认看到的是第二个section的某一页面,当每次执行定时器循环时先根据当前显示的索引以迅雷不及掩耳之势变为显示第二组的这个页面,如果这个页面是最后一个则下一个页面就是第三组数据的第一个,因此不会导致回滚的尴尬。
里面使用了isAutoScrolling来判断是否根据定时器滚动,当拖拽时定时器要注销,使用isAutoScrolling能够避免定时器与拖拽的冲突

##使用

1
2
3
4
5
6
7
let bannerView = LYAnimateBannerView(frame: CGRect(x: 0, y: 0, width: kScreenW, height: kScreenW), delegate: self)
bannerView.backgroundColor = UIColor.white
bannerView.showPageControl = true
self.bannerView.imageUrlArray = arrM
if arrM.count < 2{
self.bannerView.showPageControl = false
}

##源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
import UIKit
protocol LYAnimateBannerViewDelegate {
func LY_AnimateBannerViewClick(banner:LYAnimateBannerView,index:NSInteger)
}
class LYAnimateBannerView: UIView {
enum LY_BannerType {
case ly_titleType
case ly_imageType
case ly_imageUrlType
}
var LY_AnimateBannerViewClickBlock : ((Int) -> Void)?
var showPageControl = false
fileprivate var delegate : LYAnimateBannerViewDelegate?
fileprivate var collectionView : UICollectionView!
fileprivate var timer : Timer?
fileprivate var type : LY_BannerType = .ly_titleType
fileprivate var pageControl : UIPageControl?
fileprivate var isAutoScrolling = false
var titleArray = Array<String>(){
didSet{
self.type = .ly_titleType
self.setUpCollectionView()
if self.titleArray.count > 1{
self.addTimer()
}
}
}
var imageArray = Array<UIImage>(){
didSet{
self.type = .ly_imageType
self.setUpCollectionView()
if self.imageArray.count > 1{
self.addTimer()
}
}
}
var imageUrlArray = Array<String>(){
didSet{
self.type = .ly_imageUrlType
self.setUpCollectionView()
if self.imageUrlArray.count > 1{
self.addTimer()
}
}
}
init(frame:CGRect,delegate:LYAnimateBannerViewDelegate) {
super.init(frame: frame)
self.frame = frame
self.delegate = delegate
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpCollectionView() {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = self.type == .ly_titleType ? .vertical : .horizontal
layout.itemSize = CGSize.init(width: self.w, height: self.h)
self.collectionView = UICollectionView.init(frame: self.bounds, collectionViewLayout: layout)
self.collectionView.dataSource = self
self.collectionView.delegate = self
self.collectionView.backgroundColor = UIColor.clear
self.collectionView.showsVerticalScrollIndicator = false
self.collectionView.showsHorizontalScrollIndicator = false
self.collectionView.isPagingEnabled = true
collectionView.register(UINib.init(nibName: "BannerScrollTitleCell", bundle: Bundle.main), forCellWithReuseIdentifier: "BannerScrollTitleCell")
collectionView.register(UINib.init(nibName: "BannerScrollImageCell", bundle: Bundle.main), forCellWithReuseIdentifier: "BannerScrollImageCell")
self.addSubview(self.collectionView)
self.collectionView.reloadData()
self.setUpPageControl()
}
//set pagecontrol
func setUpPageControl() {
//少于两个的时候不用滚动
if self.collectionView.numberOfItems(inSection: 0) < 2{
return
}
if self.pageControl != nil{
self.pageControl = nil
self.pageControl?.removeFromSuperview()
}
self.pageControl = UIPageControl()
self.pageControl?.numberOfPages = self.collectionView.numberOfItems(inSection: 0)
self.pageControl?.currentPageIndicatorTintColor = UIColor.darkGray
self.pageControl?.pageIndicatorTintColor = UIColor.lightGray
self.pageControl?.frame = CGRect.init(x: 0, y: self.h - 30, width: self.w, height: 30)
self.addSubview(self.pageControl!)
}
//设置定时器
func addTimer() {
if self.timer != nil{
self.removeTimer()
}
self.timer = Timer(timeInterval: 3.0, target: self, selector: #selector(LYAnimateBannerView.nextPage), userInfo: nil, repeats: true)
RunLoop.main.add(self.timer!, forMode: .defaultRunLoopMode)
timer!.fire()
self.isAutoScrolling = true
}
//移除定时器
func removeTimer() {
self.timer?.invalidate()
self.timer = nil
}
func nextPage() {
if !self.isAutoScrolling{
return
}
// 1.马上显示回最中间那组的数据
let currentIndexPathReset = self.resetIndexPath()
// 2.计算出下一个需要展示的位置
var nextItem = currentIndexPathReset.item + 1
var nextSection = currentIndexPathReset.section
if self.type == .ly_titleType{
if nextItem == self.titleArray.count {
nextItem = 0
nextSection += 1
}
}else if self.type == .ly_imageUrlType{
if nextItem == self.imageUrlArray.count {
nextItem = 0
nextSection += 1
}
}else{
if nextItem == self.imageArray.count {
nextItem = 0
nextSection += 1
}
}
let nextIndexPath = IndexPath.init(item: nextItem, section: nextSection)
// 3.通过动画滚动到下一个位置
if self.type == .ly_titleType{
self.collectionView.scrollToItem(at: nextIndexPath, at: .top, animated: true)
}else{
self.collectionView.scrollToItem(at: nextIndexPath, at: .left, animated: true)
}
self.pageControl?.currentPage = nextItem
}
func resetIndexPath() -> IndexPath {
//current indexpath
guard let currentIndexPath = self.collectionView.indexPathsForVisibleItems.last else{
return IndexPath.init(item: 0, section: 2)
}
//马上显示回最中间那组的数据
let currentIndexPathReset = IndexPath.init(item: currentIndexPath.item, section: 2)
if self.type == .ly_titleType{
self.collectionView.scrollToItem(at: currentIndexPathReset, at: .top, animated: false)
}else{
self.collectionView.scrollToItem(at: currentIndexPathReset, at: .left, animated: false)
}
return currentIndexPathReset
}
}
extension LYAnimateBannerView : UICollectionViewDelegate, UICollectionViewDataSource{
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.type == .ly_titleType{
return self.titleArray.count
}else if self.type == .ly_imageUrlType{
return self.imageUrlArray.count
}else{
return self.imageArray.count
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if self.type == .ly_titleType{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerScrollTitleCell", for: indexPath) as! BannerScrollTitleCell
if self.titleArray.count > indexPath.row{
cell.titleLbl.text = self.titleArray[indexPath.row]
}
return cell
}else{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BannerScrollImageCell", for: indexPath) as! BannerScrollImageCell
if self.type == .ly_imageUrlType{
if self.imageUrlArray.count > indexPath.row{
cell.imgV.kf.setImage(with: URL(string:self.imageUrlArray[indexPath.row]))
}
}else{
if self.imageArray.count > indexPath.row{
cell.imgV.image = self.imageArray[indexPath.row]
}
}
return cell
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if self.LY_AnimateBannerViewClickBlock != nil{
self.LY_AnimateBannerViewClickBlock!(indexPath.row)
}else{
self.delegate?.LY_AnimateBannerViewClick(banner: self, index: indexPath.row)
}
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.removeTimer()
self.isAutoScrolling = false
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentIndexPathReset = self.resetIndexPath()
self.pageControl?.currentPage = currentIndexPathReset.item
self.addTimer()
}
}