Swift無限循環(huán)控件開發(fā)
無限循環(huán)控件是一個常常用到的一個控件,尤其是一些廣告或者應(yīng)用內(nèi)容公告通知,或者新聞滾動的設(shè)計,都是必備的。這種控件網(wǎng)上也有很多,也有很多可以自定義的版本,功能非常強(qiáng)大。 但對于我們開發(fā)者來說,在具體的應(yīng)用上風(fēng)格和樣式都是比較統(tǒng)一的,一般只需要自己特定的一種風(fēng)格或樣式即可,引入第三方顯然有點(diǎn)大材小用。那么我們怎么能簡單而且又快速的造一個無限循環(huán)的控件呢,只要我們知道無限循環(huán)的原理,那么我們就很自由的按照需求快速的完成。今天我們就講講這個‘造輪'過程。
首先我們簡單分析一下無限循環(huán)的原理。一個控件的自帶滾動有UIScrollView、UICollectionView、UITableView。我們就選這個代表性的控件來講------UICollectionView。他是一個橫向和縱向都可以高度定制的一個控件,而且也遵循Cell重用機(jī)制。
第一步,數(shù)據(jù)倍數(shù)增加,一般為3倍,我們只顯示中間那些數(shù)據(jù)即可,我們向左滑動的時候,滑到中間數(shù)據(jù)的最后一條數(shù)據(jù)的時候繼續(xù)滑動的時候要瞬間換成中間的第一條數(shù)據(jù)。如果向右滑動的時候,如果當(dāng)前是第一條數(shù)據(jù)那么就瞬間移到中間的最后一條數(shù)據(jù)上。這樣看起來就是無限循環(huán)了。一圖勝千言,有圖為證。
滑動原理很簡單,那么我怎么來用代碼實現(xiàn)呢。下面就使用代碼來實現(xiàn)這個控件。
測試環(huán)境:Xcode版本: Version 11.5 (11E608c) Mac 系統(tǒng):10.15.4 (19E266)
我們先創(chuàng)建一個工程命名為:InfiniteLoopDemo,然后我們在創(chuàng)建一個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); } }
這個是循環(huán)的主要代碼,這里需要注意一下如果只有一條數(shù)據(jù)是禁止循環(huán)的。如果需要一張循環(huán),自己可以實現(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,猛戳這里
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
swiftui開發(fā)之padding默認(rèn)值設(shè)置詳解
這篇文章主要為大家介紹了swiftui開發(fā)之padding默認(rèn)值設(shè)置詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09swift實現(xiàn)自動輪播圖效果(UIScrollView+UIPageControl+Timer)
這篇文章主要為大家詳細(xì)介紹了swift實現(xiàn)自動輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-09-09RxSwift實現(xiàn)替換delegate的方法示例
這篇文章主要給大家介紹了關(guān)于RxSwift實現(xiàn)替換delegate的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用RxSwift具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Swift解決UITableView空數(shù)據(jù)視圖問題的簡單方法
這篇文章主要給大家介紹了關(guān)于Swift解決UITableView空數(shù)據(jù)視圖問題的簡單方法,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用swift具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2018-10-10