Infinito UIScrollView

imagem

Em muitos aplicativos, você pode encontrar rolagem que nunca segue na direção oposta ao final do conteúdo. Essa técnica é padrão há muitos anos em muitas plataformas. Por outro lado, existem muitas bibliotecas de terceiros para obter esse efeito. MAS você não precisa de nenhuma biblioteca de terceiros. Essa técnica tem uma lógica muito simples.

Suporte de página O UIScrollView permite que o usuário visualize seu conteúdo página por página. O UIScrollView ativa esse efeito ajustando o deslocamento do scrollView quando o usuário terminar de arrastar. Quando o usuário rola para o final das páginas (à direita), o scrollview limita o excesso de seu conteúdo movendo seu deslocamento na direção oposta com uma bela animação.

imagem

Queremos que a visualização de rolagem não limite o deslocamento do conteúdo quando o usuário quiser exceder seu número. Portanto, precisamos adicionar mais duas páginas ao UIScrollView. A última página será adicionada ao índice zero e a primeira página será adicionada ao índice (numberOfItems + 1). Então, se o usuário visualizar a página “numberOfItems”, o deslocamento do conteúdo de rolagem x será definido como 0. Se o usuário visualizar o índice 0, o deslocamento do conteúdo de scrollView x será definido como “pageSize * numberOfItems”.

imagem

A primeira coisa a fazer é criar uma nova classe herdada do UIView.

imagem

O BannerView deve ser o seguinte:

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") } } 

Não há nada incomum aqui. Agora precisamos adicionar o código scrollView e setUp para o BannerView:

 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) } } 

Usei quadros em vez de layout automático para simplificar. Além disso, usei fechamentos em vez de delegados. Isso ajuda a evitar sujeira no ViewController. Com os fechamentos, você pode simplesmente usar o bannerView da seguinte maneira:

 // 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 } 

Para delegar o UIScrollView, usarei scrollViewDidEndDecelerating (_ scrollView: UIScrollView) em vez de scrollViewDidScroll (_ scrollView: UIScrollView). Porque não precisamos calcular a posição da troca com cada movimento scrollView.

 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) } } 

E, finalmente, nosso código será assim para o BannerView.swift:

 // 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 } } 

imagem

Sumário


Assim, criamos um componente de exibição de rolagem reutilizável com um pouco de lógica. A propósito, com grandes quantidades de dados, é melhor usar o UICollectionView, porque possui melhor desempenho e melhor gerenciamento de memória que o UIScrollView. Além disso, você pode estender o InfiniteScrollView usando as opções de sincronização ou a rolagem bidirecional. Com um pouco de aprimoramento, será uma ferramenta verdadeiramente reutilizável para seus aplicativos.

→ O código fonte completo pode ser encontrado no GitHub

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


All Articles