No Description

LLCycleScrollView.swift 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. //
  2. // LLCycleScrollView.swift
  3. // LLCycleScrollView
  4. //
  5. // Created by LvJianfeng on 2016/11/22.
  6. // Copyright © 2016年 LvJianfeng. All rights reserved.
  7. //
  8. import UIKit
  9. public enum PageControlStyle {
  10. case none
  11. case system
  12. case fill
  13. case pill
  14. case snake
  15. }
  16. public typealias LLdidSelectItemAtIndexClosure = (NSInteger) -> Void
  17. @IBDesignable open class LLCycleScrollView: UIView, UICollectionViewDelegate, UICollectionViewDataSource, UIScrollViewDelegate {
  18. // MARK: 控制参数
  19. // 是否自动滚动,默认true
  20. open var autoScroll: Bool? = true {
  21. didSet {
  22. invalidateTimer()
  23. if autoScroll! {
  24. setupTimer()
  25. }
  26. }
  27. }
  28. // 无限循环,默认true 此属性修改了就不存在轮播的意义了
  29. open var infiniteLoop: Bool? = true {
  30. didSet {
  31. if imagePaths.count > 0 {
  32. let temp = imagePaths
  33. imagePaths = temp
  34. }
  35. }
  36. }
  37. // 滚动方向,默认horizontal
  38. open var scrollDirection: UICollectionViewScrollDirection? = .horizontal {
  39. didSet {
  40. flowLayout?.scrollDirection = scrollDirection!
  41. if scrollDirection == .horizontal {
  42. position = .centeredHorizontally
  43. } else {
  44. position = .centeredVertically
  45. }
  46. }
  47. }
  48. // 滚动间隔时间,默认2s
  49. @IBInspectable open var autoScrollTimeInterval: Double = 2.0 {
  50. didSet {
  51. autoScroll = true
  52. }
  53. }
  54. // 加载状态图 -- 这个是有数据,等待加载的占位图
  55. @IBInspectable open var placeHolderImage: UIImage? = nil {
  56. didSet {
  57. if placeHolderImage != nil {
  58. placeHolderViewImage = placeHolderImage
  59. }
  60. }
  61. }
  62. // 空数据页面显示占位图 -- 这个是没有数据,整个轮播器的占位图
  63. @IBInspectable open var coverImage: UIImage? = nil {
  64. didSet {
  65. if coverImage != nil {
  66. coverViewImage = coverImage
  67. }
  68. }
  69. }
  70. // 图片显示Mode
  71. open var imageViewContentMode: UIViewContentMode? {
  72. didSet {
  73. collectionView.reloadData()
  74. }
  75. }
  76. // PageControlStyle
  77. // MARK: PageControl
  78. open var pageControlTintColor: UIColor = UIColor.lightGray {
  79. didSet {
  80. setupPageControl()
  81. }
  82. }
  83. // 当前显示颜色
  84. open var pageControlCurrentPageColor: UIColor = UIColor.white {
  85. didSet {
  86. setupPageControl()
  87. }
  88. }
  89. // MARK: CustomPageControl
  90. // 注意: 由于属性较多,所以请使用style对应的属性,如果没有标明则通用
  91. open var customPageControlStyle: PageControlStyle = .system {
  92. didSet {
  93. setupPageControl()
  94. }
  95. }
  96. // 颜色
  97. open var customPageControlTintColor: UIColor = UIColor.white {
  98. didSet {
  99. setupPageControl()
  100. }
  101. }
  102. // 间距
  103. open var customPageControlIndicatorPadding: CGFloat = 8 {
  104. didSet {
  105. setupPageControl()
  106. }
  107. }
  108. // PageControlStyle == .fill
  109. // 圆大小
  110. open var FillPageControlIndicatorRadius: CGFloat = 4 {
  111. didSet {
  112. setupPageControl()
  113. }
  114. }
  115. // PageControlStyle == .pill || PageControlStyle == .snake
  116. // 当前的颜色
  117. open var customPageControlInActiveTintColor: UIColor = UIColor(white: 1, alpha: 0.3) {
  118. didSet {
  119. setupPageControl()
  120. }
  121. }
  122. // 背景色
  123. @IBInspectable open var collectionViewBackgroundColor: UIColor! = UIColor.clear
  124. // ImagePaths
  125. open var imagePaths: Array<String> = [] {
  126. didSet {
  127. totalItemsCount = infiniteLoop! ? imagePaths.count * 100 : imagePaths.count
  128. if imagePaths.count != 1 {
  129. collectionView.isScrollEnabled = true
  130. autoScroll = true
  131. } else {
  132. collectionView.isScrollEnabled = false
  133. }
  134. setupPageControl()
  135. collectionView.reloadData()
  136. }
  137. }
  138. // 标题
  139. open var titles: Array<String> = []
  140. // MARK: Private
  141. // Identifier
  142. fileprivate let identifier = "LLCycleScrollViewCell"
  143. // 数量
  144. fileprivate var totalItemsCount: NSInteger! = 1
  145. // 显示图片(CollectionView)
  146. fileprivate var collectionView: UICollectionView!
  147. // 方向(swift后没有none,只能指定了)
  148. fileprivate var position: UICollectionViewScrollPosition! = .centeredHorizontally
  149. // FlowLayout
  150. lazy fileprivate var flowLayout: UICollectionViewFlowLayout? = {
  151. let tempFlowLayout = UICollectionViewFlowLayout()
  152. tempFlowLayout.minimumLineSpacing = 0
  153. tempFlowLayout.scrollDirection = .horizontal
  154. return tempFlowLayout
  155. }()
  156. // 计时器
  157. fileprivate var timer: Timer?
  158. // PageControl
  159. fileprivate var pageControl: UIPageControl?
  160. fileprivate var customPageControl: UIView?
  161. // 加载状态图
  162. fileprivate var placeHolderViewImage: UIImage! = UIImage(named: "LLCycleScrollView.bundle/llplaceholder.png")
  163. // 空数据页面显示占位图
  164. fileprivate var coverViewImage: UIImage! = UIImage.init(named: "LLCycleScrollView.bundle/llplaceholder.png")
  165. // 回调
  166. open var lldidSelectItemAtIndex: LLdidSelectItemAtIndexClosure?
  167. override public init(frame: CGRect) {
  168. super.init(frame: frame)
  169. // setupMainView
  170. setupMainView()
  171. }
  172. // Class func
  173. open class func llCycleScrollViewWithFrame(_ frame: CGRect, imageURLPaths: Array<String>? = [], titles: Array<String>? = [], didSelectItemAtIndex: LLdidSelectItemAtIndexClosure? = nil) -> LLCycleScrollView {
  174. let llcycleScrollView: LLCycleScrollView = LLCycleScrollView.init(frame: frame)
  175. if (imageURLPaths?.count)! > 0 {
  176. llcycleScrollView.imagePaths = imageURLPaths!
  177. }
  178. if (titles?.count)! > 0 {
  179. llcycleScrollView.titles = titles!
  180. }
  181. if didSelectItemAtIndex != nil {
  182. llcycleScrollView.lldidSelectItemAtIndex = didSelectItemAtIndex
  183. }
  184. return llcycleScrollView
  185. }
  186. // MARK: -
  187. required public init?(coder aDecoder: NSCoder) {
  188. super.init(coder: aDecoder)
  189. setupMainView()
  190. }
  191. // MARK: setupMainView
  192. private func setupMainView() {
  193. collectionView = UICollectionView.init(frame: self.bounds, collectionViewLayout: flowLayout!)
  194. collectionView.register(LLCycleScrollViewCell.self, forCellWithReuseIdentifier: identifier)
  195. collectionView.backgroundColor = collectionViewBackgroundColor
  196. collectionView.isPagingEnabled = true
  197. collectionView.showsHorizontalScrollIndicator = false
  198. collectionView.showsVerticalScrollIndicator = false
  199. collectionView.dataSource = self
  200. collectionView.delegate = self
  201. collectionView.scrollsToTop = false
  202. self.addSubview(collectionView)
  203. }
  204. // MARK: Timer
  205. func setupTimer() {
  206. timer = Timer.scheduledTimer(timeInterval: autoScrollTimeInterval as TimeInterval, target: self, selector: #selector(automaticScroll), userInfo: nil, repeats: true)
  207. RunLoop.main.add(timer!, forMode: .commonModes)
  208. }
  209. func invalidateTimer() {
  210. if timer != nil {
  211. timer?.invalidate()
  212. timer = nil
  213. }
  214. }
  215. func setupPageControl() {
  216. // 重新添加
  217. if pageControl != nil {
  218. pageControl?.removeFromSuperview()
  219. }
  220. if customPageControl != nil {
  221. customPageControl?.removeFromSuperview()
  222. }
  223. if customPageControlStyle == .none {
  224. pageControl = UIPageControl.init()
  225. pageControl?.numberOfPages = self.imagePaths.count
  226. }
  227. if customPageControlStyle == .system {
  228. pageControl = UIPageControl.init()
  229. pageControl?.pageIndicatorTintColor = pageControlTintColor
  230. pageControl?.currentPageIndicatorTintColor = pageControlCurrentPageColor
  231. pageControl?.numberOfPages = self.imagePaths.count
  232. self.addSubview(pageControl!)
  233. pageControl?.isHidden = false
  234. }
  235. if customPageControlStyle == .fill {
  236. customPageControl = LLFilledPageControl.init(frame: CGRect.zero)
  237. customPageControl?.tintColor = customPageControlTintColor
  238. (customPageControl as! LLFilledPageControl).indicatorPadding = customPageControlIndicatorPadding
  239. (customPageControl as! LLFilledPageControl).indicatorRadius = FillPageControlIndicatorRadius
  240. (customPageControl as! LLFilledPageControl).pageCount = self.imagePaths.count
  241. self.addSubview(customPageControl!)
  242. }
  243. if customPageControlStyle == .pill {
  244. customPageControl = LLPillPageControl.init(frame: CGRect.zero)
  245. (customPageControl as! LLPillPageControl).indicatorPadding = customPageControlIndicatorPadding
  246. (customPageControl as! LLPillPageControl).activeTint = customPageControlTintColor
  247. (customPageControl as! LLPillPageControl).inactiveTint = customPageControlInActiveTintColor
  248. (customPageControl as! LLPillPageControl).pageCount = self.imagePaths.count
  249. self.addSubview(customPageControl!)
  250. }
  251. if customPageControlStyle == .snake {
  252. customPageControl = LLSnakePageControl.init(frame: CGRect.zero)
  253. (customPageControl as! LLSnakePageControl).activeTint = customPageControlTintColor
  254. (customPageControl as! LLSnakePageControl).indicatorPadding = customPageControlIndicatorPadding
  255. (customPageControl as! LLSnakePageControl).indicatorRadius = FillPageControlIndicatorRadius
  256. (customPageControl as! LLSnakePageControl).inactiveTint = customPageControlInActiveTintColor
  257. (customPageControl as! LLSnakePageControl).pageCount = self.imagePaths.count
  258. self.addSubview(customPageControl!)
  259. }
  260. }
  261. // MARK: layoutSubviews
  262. override open func layoutSubviews() {
  263. super.layoutSubviews()
  264. // Cell Size
  265. flowLayout?.itemSize = self.frame.size
  266. // Page Frame
  267. if customPageControlStyle == .none || customPageControlStyle == .system {
  268. pageControl?.frame = CGRect.init(x: 0, y: self.ll_h-11, width: UIScreen.main.bounds.width, height: 10)
  269. } else {
  270. var y = self.ll_h-10-1
  271. // pill
  272. if customPageControlStyle == .pill {
  273. y+=5
  274. }
  275. let oldFrame = customPageControl?.frame
  276. customPageControl?.frame = CGRect.init(x: (oldFrame?.origin.x)!, y: y, width: (oldFrame?.size.width)!, height: 10)
  277. }
  278. if collectionView.contentOffset.x == 0 && totalItemsCount > 0 {
  279. var targetIndex = 0
  280. if infiniteLoop! {
  281. targetIndex = totalItemsCount/2
  282. }
  283. collectionView.scrollToItem(at: IndexPath.init(item: targetIndex, section: 0), at: position, animated: false)
  284. }
  285. }
  286. // MARK: Actions
  287. @objc func automaticScroll() {
  288. if totalItemsCount == 0 {return}
  289. let targetIndex = currentIndex() + 1
  290. scollToIndex(targetIndex: targetIndex)
  291. }
  292. func scollToIndex(targetIndex: Int) {
  293. if targetIndex >= totalItemsCount {
  294. if infiniteLoop! {
  295. collectionView.scrollToItem(at: IndexPath.init(item: Int(totalItemsCount/2), section: 0), at: position, animated: false)
  296. }
  297. return
  298. }
  299. collectionView.scrollToItem(at: IndexPath.init(item: targetIndex, section: 0), at: position, animated: true)
  300. }
  301. func currentIndex() -> NSInteger {
  302. if collectionView.ll_w == 0 || collectionView.ll_h == 0 {
  303. return 0
  304. }
  305. var index = 0
  306. if flowLayout?.scrollDirection == UICollectionViewScrollDirection.horizontal {
  307. index = NSInteger(collectionView.contentOffset.x + (flowLayout?.itemSize.width)! * 0.5)/NSInteger((flowLayout?.itemSize.width)!)
  308. } else {
  309. index = NSInteger(collectionView.contentOffset.y + (flowLayout?.itemSize.height)! * 0.5)/NSInteger((flowLayout?.itemSize.height)!)
  310. }
  311. return index
  312. }
  313. func pageControlIndexWithCurrentCellIndex(index: NSInteger) -> (Int) {
  314. return Int(index % imagePaths.count)
  315. }
  316. // MARK: UICollectionViewDataSource
  317. open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  318. return totalItemsCount
  319. }
  320. open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  321. let cell: LLCycleScrollViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as! LLCycleScrollViewCell
  322. // 0==count 占位图
  323. if imagePaths.count == 0 {
  324. cell.imageView.image = coverViewImage
  325. } else {
  326. let itemIndex = pageControlIndexWithCurrentCellIndex(index: indexPath.item)
  327. let imagePath = imagePaths[itemIndex]
  328. // Mode
  329. if let imageViewContentMode = imageViewContentMode {
  330. cell.imageView.contentMode = imageViewContentMode
  331. }
  332. // 根据imagePath,来判断是网络图片还是本地图
  333. if imagePath.hasPrefix("http") {
  334. cell.imageView.setImageWithNullableURL(imagePath, placeholderImage: placeHolderImage)
  335. } else {
  336. if let image = UIImage(named: imagePath) {
  337. cell.imageView.image = image
  338. } else {
  339. cell.imageView.image = UIImage(contentsOfFile: imagePath)
  340. }
  341. }
  342. // 对冲数据判断
  343. if itemIndex <= titles.count-1 {
  344. cell.title = titles[itemIndex]
  345. } else {
  346. cell.title = ""
  347. }
  348. }
  349. return cell
  350. }
  351. open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  352. if let didSelectItemAtIndexPath = lldidSelectItemAtIndex {
  353. didSelectItemAtIndexPath(pageControlIndexWithCurrentCellIndex(index: indexPath.item))
  354. }
  355. }
  356. // MARK: UIScrollViewDelegate
  357. open func scrollViewDidScroll(_ scrollView: UIScrollView) {
  358. if imagePaths.count == 0 { return }
  359. let indexOnPageControl = pageControlIndexWithCurrentCellIndex(index: currentIndex())
  360. if customPageControlStyle == .none || customPageControlStyle == .system {
  361. pageControl?.currentPage = indexOnPageControl
  362. } else {
  363. var progress: CGFloat = 999
  364. // 方向
  365. if scrollDirection == .horizontal {
  366. let currentOffsetX = scrollView.contentOffset.x - (CGFloat(totalItemsCount) * scrollView.frame.size.width) / 2
  367. if currentOffsetX == CGFloat(self.imagePaths.count) * scrollView.frame.size.width && infiniteLoop! {
  368. collectionView.scrollToItem(at: IndexPath.init(item: Int(totalItemsCount/2), section: 0), at: position, animated: false)
  369. }
  370. progress = currentOffsetX / scrollView.frame.size.width
  371. } else if scrollDirection == .vertical {
  372. let currentOffsetY = scrollView.contentOffset.y - (CGFloat(totalItemsCount) * scrollView.frame.size.height) / 2
  373. if currentOffsetY == CGFloat(self.imagePaths.count) * scrollView.frame.size.height && infiniteLoop! {
  374. collectionView.scrollToItem(at: IndexPath.init(item: Int(totalItemsCount/2), section: 0), at: position, animated: false)
  375. }
  376. progress = currentOffsetY / scrollView.frame.size.height
  377. }
  378. if progress == 999 {
  379. progress = CGFloat(indexOnPageControl)
  380. }
  381. // progress
  382. if customPageControlStyle == .fill {
  383. (customPageControl as! LLFilledPageControl).progress = progress
  384. } else if customPageControlStyle == .pill {
  385. (customPageControl as! LLPillPageControl).progress = progress
  386. } else if customPageControlStyle == .snake {
  387. (customPageControl as! LLSnakePageControl).progress = progress
  388. }
  389. }
  390. }
  391. open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
  392. if autoScroll! {
  393. invalidateTimer()
  394. }
  395. }
  396. open func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  397. if autoScroll! {
  398. setupTimer()
  399. }
  400. }
  401. }