Unendliche UIScrollView

Bild

In vielen Anwendungen tritt möglicherweise ein Bildlauf auf, der am Ende des Inhalts niemals in die entgegengesetzte Richtung verläuft. Diese Technik ist seit vielen Jahren auf vielen Plattformen Standard. Auf der anderen Seite gibt es viele Bibliotheken von Drittanbietern, um diesen Effekt zu erzielen. ABER Sie benötigen keine Bibliothek von Drittanbietern. Diese Technik hat eine sehr einfache Logik.

Seitenunterstützung Mit UIScrollView kann der Benutzer den Inhalt Seite für Seite anzeigen. UIScrollView aktiviert diesen Effekt, indem der ScrollView-Versatz angepasst wird, wenn der Benutzer das Ziehen beendet hat. Wenn der Benutzer zum Ende der Seiten (rechts) blättert, begrenzt die Bildlaufansicht den Überschuss des Inhalts, indem der Versatz mit einer schönen Animation in die entgegengesetzte Richtung verschoben wird.

Bild

Wir möchten, dass die Bildlaufansicht den Inhaltsversatz nicht einschränkt, wenn der Benutzer seine Anzahl überschreiten möchte. Daher müssen wir der UIScrollView zwei weitere Seiten hinzufügen. Die letzte Seite wird dem Nullindex hinzugefügt, und die erste Seite wird dem Index hinzugefügt (numberOfItems + 1). Wenn der Benutzer die Seite "numberOfItems" anzeigt, wird der Versatz für Bildlaufinhalt x auf 0 gesetzt. Wenn der Benutzer den Index 0 anzeigt, wird der Versatz für Bildlauf x Inhalt auf "pageSize * numberOfItems" gesetzt.

Bild

Als erstes müssen Sie eine neue Klasse erstellen, die von UIView geerbt wurde.

Bild

BannerView sollte wie folgt aussehen:

import UIKit class BannerView: UIView { override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 

Hier ist nichts Ungewöhnliches. Jetzt müssen wir den scrollView- und setUp-Code für das BannerView hinzufügen:

 import UIKit class BannerView: UIView { private let scrollView:UIScrollView = { let sc = UIScrollView(frame: .zero) sc.translatesAutoresizingMaskIntoConstraints = false sc.isPagingEnabled = true return sc }() // BannerView DataSources (1) private var itemAtIndex:((_ bannerView:BannerView , _ index:Int)->(UIView))! private var numberOfItems:Int = 0 override init(frame: CGRect) { super.init(frame: frame) setUpUI() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setUpUI() { scrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) scrollView.delegate = self self.addSubview(scrollView) scrollView.showsHorizontalScrollIndicator = false } func reloadData(numberOfItems:Int , itemAtIndex:@escaping ((_ bannerView:BannerView , _ index:Int)->(UIView)) ) { self.itemAtIndex = itemAtIndex self.numberOfItems = numberOfItems reloadScrollView() } private func reloadScrollView() { guard self.numberOfItems > 0 else { return } if self.numberOfItems == 1 { let firstItem:UIView = self.itemAtIndex(self , 0) addViewToIndex(view: firstItem, index: 0) scrollView.isScrollEnabled = false return } let firstItem:UIView = self.itemAtIndex(self , 0) addViewToIndex(view: firstItem, index: numberOfItems+1) let lastItem:UIView = self.itemAtIndex(self , numberOfItems-1) addViewToIndex(view: lastItem, index: 0) for index in 0..<self.numberOfItems { let item:UIView = self.itemAtIndex(self , index) addViewToIndex(view: item, index: index+1) } scrollView.contentSize = CGSize(width: CGFloat(numberOfItems+2)*scrollView.frame.size.width, height: scrollView.frame.size.height) scrollView.contentOffset = CGPoint(x: self.scrollView.frame.size.width, y: self.scrollView.contentOffset.y) } private func addViewToIndex(view:UIView, index:Int) { view.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(view) view.frame = CGRect(x: CGFloat(index)*scrollView.frame.size.width, y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height) } } 

Der Einfachheit halber habe ich Frames anstelle des automatischen Layouts verwendet. Außerdem habe ich Verschlüsse anstelle von Delegierten verwendet. Dies hilft, Schmutz im ViewController zu vermeiden. Bei Verschlüssen können Sie bannerView einfach wie folgt verwenden:

 // ViewController.swift bannerView = BannerView(frame: CGRect(x: 0, y: 64, width: self.view.frame.size.width, height: 200)) self.view.addSubview(bannerView) bannerView.reloadData(numberOfItems: 5) { (bannerView, index) -> (UIView) in let view = UIView() view.backgroundColor = UIColor.red return view } 

Zum Delegieren von UIScrollView verwende ich scrollViewDidEndDecelerating (_ scrollView: UIScrollView) anstelle von scrollViewDidScroll (_ scrollView: UIScrollView). Weil wir die Swap-Position nicht bei jeder scrollView-Bewegung berechnen müssen.

 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let currentPage:Int = Int(scrollView.contentOffset.x / scrollView.frame.size.width) if currentPage == 0 { self.scrollView.contentOffset = CGPoint(x: scrollView.frame.size.width * CGFloat(numberOfItems), y: scrollView.contentOffset.y) } else if currentPage == numberOfItems { self.scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y) } } 

Und schließlich wird unser Code für BannerView.swift so aussehen:

 // BannerView.swift import UIKit class BannerView: UIView , UIScrollViewDelegate{ private let scrollView:UIScrollView = { let sc = UIScrollView(frame: .zero) sc.translatesAutoresizingMaskIntoConstraints = false sc.isPagingEnabled = true return sc }() private var itemAtIndex:((_ bannerView:BannerView , _ index:Int)->(UIView))! private var numberOfItems:Int = 0 override init(frame: CGRect) { super.init(frame: frame) setUpUI() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func reloadData(configuration:BannerViewConfiguration? , numberOfItems:Int , itemAtIndex:@escaping ((_ bannerView:BannerView , _ index:Int)->(UIView)) ) { self.itemAtIndex = itemAtIndex self.numberOfItems = numberOfItems reloadScrollView() } private func reloadScrollView() { guard self.numberOfItems > 0 else { return } if self.numberOfItems == 1 { let firstItem:UIView = self.itemAtIndex(self , 0) addViewToIndex(view: firstItem, index: 0) scrollView.isScrollEnabled = false return } let firstItem:UIView = self.itemAtIndex(self , 0) addViewToIndex(view: firstItem, index: numberOfItems+1) let lastItem:UIView = self.itemAtIndex(self , numberOfItems-1) addViewToIndex(view: lastItem, index: 0) for index in 0..<self.numberOfItems { let item:UIView = self.itemAtIndex(self , index) addViewToIndex(view: item, index: index+1) } scrollView.contentSize = CGSize(width: CGFloat(numberOfItems+2)*scrollView.frame.size.width, height: scrollView.frame.size.height) scrollView.contentOffset = CGPoint(x: self.scrollView.frame.size.width, y: self.scrollView.contentOffset.y) } private func addViewToIndex(view:UIView, index:Int) { view.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(view) view.frame = CGRect(x: CGFloat(index)*scrollView.frame.size.width, y: 0, width: scrollView.frame.size.width, height: scrollView.frame.size.height) } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let currentPage:Int = Int(scrollView.contentOffset.x / scrollView.frame.size.width) if currentPage == 0 { self.scrollView.contentOffset = CGPoint(x: scrollView.frame.size.width * CGFloat(numberOfItems), y: scrollView.contentOffset.y) } else if currentPage == numberOfItems { self.scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y) } } private func setUpUI() { scrollView.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height) scrollView.delegate = self self.addSubview(scrollView) scrollView.showsHorizontalScrollIndicator = false } } 

Bild

Zusammenfassung


Daher haben wir eine wiederverwendbare Scrollview-Komponente mit ein wenig Logik erstellt. Übrigens ist es bei großen Datenmengen besser, UICollectionView zu verwenden, da es eine bessere Leistung und eine bessere Speicherverwaltung als UIScrollView bietet. Darüber hinaus können Sie InfiniteScrollView mithilfe der Synchronisierungsoptionen oder des bidirektionalen Bildlaufs erweitern. Mit einer kleinen Verbesserung wird es ein wirklich wiederverwendbares Werkzeug für Ihre Anwendungen sein.

→ Den vollständigen Quellcode finden Sie auf GitHub

Source: https://habr.com/ru/post/de453782/


All Articles