Swift无限循环控件开发

 更新时间:2020年07月28日 09:42:12   作者:海阔任月飞  
这篇文章主要为大家详细介绍了Swift无限循环控件开发,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

无限循环控件是一个常常用到的一个控件,尤其是一些广告或者应用内容公告通知,或者新闻滚动的设计,都是必备的。这种控件网上也有很多,也有很多可以自定义的版本,功能非常强大。 但对于我们开发者来说,在具体的应用上风格和样式都是比较统一的,一般只需要自己特定的一种风格或样式即可,引入第三方显然有点大材小用。那么我们怎么能简单而且又快速的造一个无限循环的控件呢,只要我们知道无限循环的原理,那么我们就很自由的按照需求快速的完成。今天我们就讲讲这个‘造轮'过程。

首先我们简单分析一下无限循环的原理。一个控件的自带滚动有UIScrollView、UICollectionView、UITableView。我们就选这个代表性的控件来讲------UICollectionView。他是一个横向和纵向都可以高度定制的一个控件,而且也遵循Cell重用机制。

第一步,数据倍数增加,一般为3倍,我们只显示中间那些数据即可,我们向左滑动的时候,滑到中间数据的最后一条数据的时候继续滑动的时候要瞬间换成中间的第一条数据。如果向右滑动的时候,如果当前是第一条数据那么就瞬间移到中间的最后一条数据上。这样看起来就是无限循环了。一图胜千言,有图为证。

滑动原理很简单,那么我怎么来用代码实现呢。下面就使用代码来实现这个控件。

测试环境:Xcode版本: Version 11.5 (11E608c)    Mac 系统:10.15.4 (19E266)

我们先创建一个工程命名为:InfiniteLoopDemo,然后我们在创建一个InfiniteLoopContentView视图。代码如下:

import UIKit
 
protocol InfiniteLoopContentViewDelegate: NSObjectProtocol {
 
 func infiniteLoopView(loopView: InfiniteLoopContentView,index: Int) -> UICollectionViewCell;
 func numberCountOfRows(loopView: InfiniteLoopContentView) -> Int;
 func infiniteLoopView(loopView: InfiniteLoopContentView,didSelectedIndexPath index: Int);
 
 func didEndScrollView(loopView: InfiniteLoopContentView) -> Void
}
 
extension InfiniteLoopContentViewDelegate {
 
 func didEndScrollView(loopView: InfiniteLoopContentView) {
 
 }
}
 
class InfiniteLoopContentView: UICollectionView {
 
 private var perContentSize: CGFloat {
 return contentSize.width / 3;
 }
 
 weak var infiniteDelegate: InfiniteLoopContentViewDelegate!
 
 private var perCount = 0;
 
 private var isAutoScroll = false;
 
 private let runDiration: Double = 3.2;
 
 weak fileprivate var pageControl: UIPageControl!
 
 var beginTimer = true {
 didSet{
  runTimer();
 }
 }
 
 private var width: CGFloat {
 frame.width
 }
 private var height: CGFloat {
 frame.height
 }
 
 private func runTimer() -> Void {
 if beginTimer {
  NSObject.cancelPreviousPerformRequests(withTarget: self);
  perform(#selector(runTimerAction), with: nil, afterDelay: runDiration);
 }else {
  NSObject.cancelPreviousPerformRequests(withTarget: self);
  isAutoScroll = false;
 }
 }
 
 
 
 @objc func runTimerAction() -> Void {
 if perCount <= 1 || contentSize.width < self.width {
  return;
 }
 let offsetx = contentOffset.x;
 guard let indexPath = indexPathForItem(at: .init(x: offsetx + width/2, y: height/2)) else{
  return;
 }
 isAutoScroll = true;
 var next = indexPath.row + 1;
 if next >= (perCount * 3 - 1) {
  next = perCount * 3 - 1;
  
  UIView.animate(withDuration: 0.3, animations: { 
  self.scrollToItem(at: .init(row: next, section: 0), at: .centeredHorizontally, animated: false);
  }) { (finished) in
  self.pageControl?.currentPage = self.perCount - 1;
  self.contentOffset = .init(x: (self.perCount - 1) * Int(self.width), y: 0);
  }
  
 }else{
  scrollToItem(at: .init(row: next, section: 0), at: .centeredHorizontally, animated: true);
  pageControl?.currentPage = next % perCount;
 }
 perform(#selector(runTimerAction), with: nil, afterDelay: runDiration);
 
 }
 
 override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
 super.init(frame: frame, collectionViewLayout: layout);
 if let subLayout = layout as? UICollectionViewFlowLayout {
  subLayout.scrollDirection = .horizontal;
  subLayout.minimumLineSpacing = 0;
  subLayout.minimumInteritemSpacing = 0;
  subLayout.itemSize = .init(width: width, height: height);
 }
 showsHorizontalScrollIndicator = false;
 showsVerticalScrollIndicator = false;
 isPagingEnabled = true;
 delegate = self;
 dataSource = self;
 backgroundColor = UIColor.systemBackground;
 
 runTimer();
 }
 
 deinit {
 infiniteDelegate = nil;
 beginTimer = false;
 }
 
 
 required init?(coder: NSCoder) {
 fatalError("init(coder:) has not been implemented")
 }
 
 override func layoutSubviews() {
 super.layoutSubviews();
 
 if perCount <= 1 || isAutoScroll {
  return;
 }
 
 if contentSize.width < self.width {
  return;
 }
 
 
 let contentOffset = self.contentOffset;
 
 if contentOffset.x >= (perContentSize * 2) {
  let offset = contentOffset.x - (perContentSize * 2);
  self.contentOffset = .init(x: perContentSize + offset, y: 0);
 }else if contentOffset.x < perContentSize {
  let offset = Int(contentOffset.x) % Int(perContentSize); 
  self.contentOffset = .init(x: perContentSize + CGFloat(offset), y: 0);
 }
 pageControl?.currentPage = Int((contentOffset.x + width/2) / width) % perCount;
 
 
 }
}
 
extension InfiniteLoopContentView: UICollectionViewDelegateFlowLayout,UICollectionViewDataSource{
 // MARK: - collection view delegate and dataSource
 
 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
 perCount = infiniteDelegate?.numberCountOfRows(loopView: self) ?? 0
 if perCount == 1 {
  return perCount;
 }
 return perCount * 3;
 }
 
 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
 return collectionView.bounds.size;
 }
 
 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
 return infiniteDelegate.infiniteLoopView(loopView: self, index: indexPath.row % perCount);
 }
 
 func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
 infiniteDelegate.infiniteLoopView(loopView: self, didSelectedIndexPath: indexPath.row % perCount);
 }
 
}
 
extension InfiniteLoopContentView {
 func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
 beginTimer = false;
 }
 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
 beginTimer = true;
 infiniteDelegate?.didEndScrollView(loopView: self);
 
 }
 func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
 if !decelerate {
  scrollViewDidEndDecelerating(scrollView);
 }
 }
 func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
 scrollViewDidEndDecelerating(scrollView);
 }
}

这个是循环的主要代码,这里需要注意一下如果只有一条数据是禁止循环的。如果需要一张循环,自己可以实现以下。

使用的方法和UICollectionView一样,我们来看具体使用方式:

import UIKit
 
class MainViewController: UIViewController {
 
 
 
 var loopView: InfiniteLoopContentView!
 
 
 override func viewDidLoad() {
 super.viewDidLoad()
 
 // Do any additional setup after loading the view.
 
 
 let layout = UICollectionViewFlowLayout();
 loopView = InfiniteLoopContentView(frame: .init(x: 0, y: 200, width: view.frame.width, height: 200), collectionViewLayout: layout);
 view.addSubview(loopView);
 loopView.infiniteDelegate = self;
 loopView.register(LoopViewCell.self, forCellWithReuseIdentifier: "cell");
 loopView.reloadData();
 }
 
 
 
}
 
extension MainViewController: InfiniteLoopContentViewDelegate{
 func infiniteLoopView(loopView: InfiniteLoopContentView, index: Int) -> UICollectionViewCell {
 let cell = loopView.dequeueReusableCell(withReuseIdentifier: "cell", for: .init(row: index, section: 0)) as! LoopViewCell;
 cell.imageView.image = UIImage(named: (index + 1).description);
 
 return cell;
 }
 
 func numberCountOfRows(loopView: InfiniteLoopContentView) -> Int {
 return 3;
 }
 
 func infiniteLoopView(loopView: InfiniteLoopContentView, didSelectedIndexPath index: Int) {
 
 }
 
 
}
 
 
class LoopViewCell: UICollectionViewCell {
 var imageView: UIImageView!
 override init(frame: CGRect) {
 super.init(frame: frame);
 imageView = UIImageView(frame: bounds);
 imageView.contentMode = .scaleAspectFit;
 addSubview(imageView);
 backgroundColor = UIColor.black
 }
 
 required init?(coder: NSCoder) {
 fatalError("init(coder:) has not been implemented")
 }
}

这是SwiftUI创建的工程,所以我们可以只用使用最新的Canvars来预览效果就好。如下:

struct ContentView: View {
 var body: some View {
 
 ViewController()
 }
}
 
struct ContentView_Previews: PreviewProvider {
 static var previews: some View {
 ContentView()
 }
}
 
struct ViewController: UIViewControllerRepresentable {
 func makeUIViewController(context: Context) -> MainViewController {
 MainViewController()
 }
 
 func updateUIViewController(_ uiViewController: MainViewController, context: Context) {
 
 }
 
 typealias UIViewControllerType = MainViewController 
 
 
}

预览的效果如下:

最后上传上Demo,猛戳这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • swiftui开发之padding默认值设置详解

    swiftui开发之padding默认值设置详解

    这篇文章主要为大家介绍了swiftui开发之padding默认值设置详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • swift实现自动轮播图效果(UIScrollView+UIPageControl+Timer)

    swift实现自动轮播图效果(UIScrollView+UIPageControl+Timer)

    这篇文章主要为大家详细介绍了swift实现自动轮播图效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-09-09
  • 判断 ScrollView List 是否正在滚动详解

    判断 ScrollView List 是否正在滚动详解

    这篇文章主要为大家介绍了判断 ScrollView、List 是否正在滚动示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Swift使用CoreData时遇到的一些填坑记录

    Swift使用CoreData时遇到的一些填坑记录

    这篇文章主要给大家记录了在Swift使用CoreData时遇到的一些坑,以及介绍了CoreData在Swift 3.0中的一点改变,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-12-12
  • Swift编程之枚举类型详解

    Swift编程之枚举类型详解

    这篇文章主要介绍了Swift编程之枚举类型,讲解了枚举语法、匹配枚举值与switch语句、关联值、原始值等内容,Swift中枚举类型是最重要的类型,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • OpenStack的Swift组件详解

    OpenStack的Swift组件详解

    这篇文章主要介绍了OpenStack的Swift组件,对swift感兴趣的同学,可以参考下
    2021-04-04
  • RxSwift实现替换delegate的方法示例

    RxSwift实现替换delegate的方法示例

    这篇文章主要给大家介绍了关于RxSwift实现替换delegate的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用RxSwift具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-09-09
  • 通过示例分析Swift单例模式

    通过示例分析Swift单例模式

    这篇文章主要介绍了通过示例分析Swift单例模式的三种方法,分别是全局变量,内部变量,dispatch_once方式,有需要的小伙伴可以参考下。
    2015-06-06
  • Swift解决UITableView空数据视图问题的简单方法

    Swift解决UITableView空数据视图问题的简单方法

    这篇文章主要给大家介绍了关于Swift解决UITableView空数据视图问题的简单方法,文中通过示例代码介绍的非常详细,对大家学习或者使用swift具有一定的参考学习价值,需要的朋友可以参考下
    2018-10-10
  • Swift能代替Objective-C吗?

    Swift能代替Objective-C吗?

    这是我在网上上看到的答案,复制粘贴过来和大家分享一下,因为我和很多人一样很关心Swift的出现对Mac开发的影响和对Objective-C的影响。
    2014-09-09

最新评论