Swift無限循環(huán)控件開發(fā)
無限循環(huán)控件是一個(gè)常常用到的一個(gè)控件,尤其是一些廣告或者應(yīng)用內(nèi)容公告通知,或者新聞滾動(dòng)的設(shè)計(jì),都是必備的。這種控件網(wǎng)上也有很多,也有很多可以自定義的版本,功能非常強(qiáng)大。 但對(duì)于我們開發(fā)者來說,在具體的應(yīng)用上風(fēng)格和樣式都是比較統(tǒng)一的,一般只需要自己特定的一種風(fēng)格或樣式即可,引入第三方顯然有點(diǎn)大材小用。那么我們?cè)趺茨芎?jiǎn)單而且又快速的造一個(gè)無限循環(huán)的控件呢,只要我們知道無限循環(huán)的原理,那么我們就很自由的按照需求快速的完成。今天我們就講講這個(gè)‘造輪'過程。
首先我們簡(jiǎn)單分析一下無限循環(huán)的原理。一個(gè)控件的自帶滾動(dòng)有UIScrollView、UICollectionView、UITableView。我們就選這個(gè)代表性的控件來講------UICollectionView。他是一個(gè)橫向和縱向都可以高度定制的一個(gè)控件,而且也遵循Cell重用機(jī)制。
第一步,數(shù)據(jù)倍數(shù)增加,一般為3倍,我們只顯示中間那些數(shù)據(jù)即可,我們向左滑動(dòng)的時(shí)候,滑到中間數(shù)據(jù)的最后一條數(shù)據(jù)的時(shí)候繼續(xù)滑動(dòng)的時(shí)候要瞬間換成中間的第一條數(shù)據(jù)。如果向右滑動(dòng)的時(shí)候,如果當(dāng)前是第一條數(shù)據(jù)那么就瞬間移到中間的最后一條數(shù)據(jù)上。這樣看起來就是無限循環(huán)了。一圖勝千言,有圖為證。

滑動(dòng)原理很簡(jiǎn)單,那么我怎么來用代碼實(shí)現(xiàn)呢。下面就使用代碼來實(shí)現(xiàn)這個(gè)控件。
測(cè)試環(huán)境:Xcode版本: Version 11.5 (11E608c) Mac 系統(tǒng):10.15.4 (19E266)
我們先創(chuàng)建一個(gè)工程命名為:InfiniteLoopDemo,然后我們?cè)趧?chuàng)建一個(gè)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);
}
}
這個(gè)是循環(huán)的主要代碼,這里需要注意一下如果只有一條數(shù)據(jù)是禁止循環(huán)的。如果需要一張循環(huán),自己可以實(shí)現(xiàn)以下。
使用的方法和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創(chuàng)建的工程,所以我們可以只用使用最新的Canvars來預(yù)覽效果就好。如下:
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
}
預(yù)覽的效果如下:

最后上傳上Demo,猛戳這里
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Swift UIButton使用教程
- Swift 進(jìn)階 —— map 和 flatMap的使用
- 深入探究Swift枚舉關(guān)聯(lián)值的內(nèi)存
- swift4.2實(shí)現(xiàn)新聞首頁(yè)導(dǎo)航
- IOS在SwiftUI中顯示模態(tài)視圖的實(shí)例代碼
- iOS SwiftUI 顏色漸變填充效果的實(shí)現(xiàn)
- Swift 去除 TableView 多余的空Cell中的橫線的方法
- 詳解Swift 之clipped是什么如何用
- SwiftUI中級(jí)List如何添加新內(nèi)容(2020年教程)
- Swift如何優(yōu)雅的進(jìn)行解包
相關(guān)文章
switch實(shí)現(xiàn)一個(gè)兩數(shù)的運(yùn)算代碼示例
這篇文章主要介紹了switch實(shí)現(xiàn)一個(gè)兩數(shù)的運(yùn)算代碼示例,需要的朋友可以參考下2017-06-06
SwiftUI使用Paths和AnimatableData實(shí)現(xiàn)酷炫的顏色切換動(dòng)畫
這篇文章主要介紹了SwiftUI使用Paths和AnimatableData實(shí)現(xiàn)酷炫的顏色切換動(dòng)畫,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-05-05
本地推送通知UserNotifications在Swift中的實(shí)現(xiàn)方式
這篇文章主要介紹了本地推送通知UserNotifications在Swift中的實(shí)現(xiàn)方式,想了解消息推送的同學(xué),一定要看一下2021-04-04

