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,animated: true); pageControl?.currentPage = next % perCount; } perform(#selector(runTimerAction),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,猛戳這裡
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。