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,猛戳这里

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

(0)

相关推荐

  • Swift中循环语句中的转移语句 break 和 continue

    下面通过实例代码给大家介绍了Swift中循环语句中的转移语句 break 和 continue,具体代码如下所示: /** 循环语句中的转移语句 break 和 continue */ let array:Array = [3, 4, 5, 6, 7, 8, 9] for k in array { if k == 5 { print(k) break } } print("--------->") for k in array { if k == 5 { // 结束本次循环,进入

  • Swift循环遍历集合的方法总结分享

    前言 之前分享总结过OC循环遍历,文章点击这里:iOS遍历集合(NSArray,NSDictionary.NSSet)方法总结.随着Swift的逐渐完善,自己使用Swift开发的项目经验和知识逐渐积累,是时候总结一下Swift的循环遍历了.相信Swift一定会给你一些不一样的东西,甚至是惊喜,感兴趣的朋友们下面来一起看看吧. 第一种方式:for-in循环 OC延续了C语言的for循环,在Swift中被彻底改造,我们无法再使用传统形式的for循环了 遍历数组和字典: //遍历数组 let iosA

  • Swift流程控制之循环语句和判断语句详解

    Swift提供了所有c类语言的控制流结构.包括for和while循环来执行一个任务多次:if和switch语句来执行确定的条件下不同的分支的代码:break和continue关键字能将运行流程转到你代码的另一个点上. 除了C语言传统的for-condition-increment循环,Swift加入了for-in循环,能更加容易的遍历arrays, dictionaries, ranges, strings等其他序列类型. Swift的switch语句也比C语言的要强大很多. Swift中swi

  • Swift无限循环控件开发

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

  • swift自定义表格控件(UITableView)

    本文实例为大家分享了swift自定义表格控件的具体代码,供大家参考,具体内容如下 1.效果图 2.控件 storyboard上的控件就2个:UIButton. 3.为按钮添加点击事件 通过辅助编辑器为这2个按钮添加按钮单击事件:分别为 generalBtnClick 和   groupBtnClick 4.完整代码 import UIKit enum UIControlType{     case Basic     case Advanced } class ViewController: U

  • ASP.NET 控件开发系列之图片切换web控件

    贴出来控件页面的代码. PicList.ascx 复制代码 代码如下: <%@ Control Language="C#" AutoEventWireup="true" CodeFile="PicList.ascx.cs" Inherits="WebParts_PicList" %> <style type="text/css"> /* Reset style */ * { marg

  • 控件开发时两种JS嵌入资源方式的使用方法

    第一种: 直接把要嵌入的JS文件属性设置为"嵌入的资源".protected override void OnInit(EventArgs e) {       base.OnInit (e);        if(!base.Page.IsStartupScriptRegistered("Script"))         {                 Assembly assembly = typeof(TestControl).Assembly;    

  • Android实现仪表盘控件开发

    仪表盘在工业软件中很常见,今天整一个图片式仪表盘控件(非几何图形绘制).实现非常简单,一张背景图,一张指针.创建一个RelativeLayout布局文件,然后在里面布置好控件的位置,代码如下 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" androi

  • 使用VS2010 C#开发ActiveX控件(上)

    要开发Web版的读卡程序,大体思路如下: 1.       使用C#对原始的Dll进行封装,这里要封装两部分内容,一部分是串口通信的功能,一部分是对卡读写的功能. 2.       开发ActiveX控件调用封装后的Dll,使用串口通信来对卡进行读写. 3.       打包并发布ActiveX控件. 4.  使用ActiveX控件. 思路1中封装代码有2个类SerialInterfaceHelper,串口通信的帮助类,MifareOneHelper,M1卡的读写帮助类,我们放在了项目CardR

  • Repeater控件与PagedDataSource结合实现分页功能

    本文讲解Repeater控件与PagedDataSource相结合实现其分页功能.PagedDataSource 类封装那些允许数据源控件(如 DataGrid.GridView)执行分页操作的属性.如果控件开发人员需对自定义数据绑定控件提供分页支持,即可使用此类. PagedDataSource 类的部分公共属性: AllowCustomPaging // 获取或设置指示是否启用自定义分页的值. AllowPaging // 获取或设置指示是否启用分页的值. Count // 获取要从数据源使

  • C#实现多选项卡的浏览器控件

    本文详细为大家分享了C#多选项卡的浏览器控件的设计与实现,供大家参考,具体内容如下 1.  为什么我们需要多选项卡的浏览器控件 项目中需要使用WinForm应用程序来包装BS应用程序的浏览器外壳,在.NET的WebBrowser中没有多选项卡浏览的自带配置属性,我们需要实现多选项卡的浏览器控件来实现包装BS应用程序的目的,而不会弹出IE浏览器窗口. 2. 我们需要了解哪些知识点 2.1.     WebBrowser控件 WebBrowser 控件为 WebBrowser ActiveX 控件提

  • JS 控件事件小结

    概述: 事件对于控件来说至关重要,控件的消息通信机制使用事件的成本最低,但是对于JS控件来说有一些麻烦需要解决,JS类本身不支持事件,DOM模型支持的事件仅适应于浏览器的DOM节点.所以创建一套事件是我们写控件之前要做的. 事件机制 对于事件的机制我不想多说,各种语言中对事件的描述都很具体,都是观察者模式的一种实现,我们可以从中抽取出事件必须的接口(由于控件库是基于jQuery 所以接口跟jquery保持一致): 1.on: 绑定事件 2.off: 删除事件 3.fire: 触发事件 4.add

  • ASP.NET中的Web控件介绍

    目录 一.HTML控件 二.HTML服务器控件 三.Web服务器控件 Web服务器控件和html服务器控件的区别 四.Web用户控件 五.Web自定义控件 1.用户控件和自定义控件的异同 2.实现自定义控件 3.复合自定义控件 1.ASP.NET登录控件的开发 2.ASP.NET登录控件的使用 4.自定义分页控件 一.HTML控件 就是我们通常的说的html语言标记,这些语言标记在已往的静态页面和其他网页里存在,不能在服务器端控制的,只能在客户端通过javascript等程序语言来控制. <in

随机推荐