ios uiscrollview uicollectionview infinite-scroll

ios - Ver con desplazamiento continuo; tanto horizontal como vertical



uiscrollview uicollectionview (4)

@updated para swift 3 y cambió la forma en que se calcula el maxRow; de lo contrario, la última columna es de corte y puede causar errores

import UIKit class NodeMap : UICollectionViewController { var rows = 10 var cols = 10 override func viewDidLoad(){ self.collectionView!.collectionViewLayout = NodeLayout(itemWidth: 400.0, itemHeight: 300.0, space: 5.0) } override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return rows } override func numberOfSections(in collectionView: UICollectionView) -> Int { return cols } override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { return collectionView.dequeueReusableCell(withReuseIdentifier: "node", for: indexPath) } } class NodeLayout : UICollectionViewFlowLayout { var itemWidth : CGFloat var itemHeight : CGFloat var space : CGFloat var columns: Int{ return self.collectionView!.numberOfItems(inSection: 0) } var rows: Int{ return self.collectionView!.numberOfSections } init(itemWidth: CGFloat, itemHeight: CGFloat, space: CGFloat) { self.itemWidth = itemWidth self.itemHeight = itemHeight self.space = space super.init() } required init(coder aDecoder: NSCoder) { self.itemWidth = 50 self.itemHeight = 50 self.space = 3 super.init() } override var collectionViewContentSize: CGSize{ let w : CGFloat = CGFloat(columns) * (itemWidth + space) let h : CGFloat = CGFloat(rows) * (itemHeight + space) return CGSize(width: w, height: h) } override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { let attributes = UICollectionViewLayoutAttributes(forCellWith: indexPath) let x : CGFloat = CGFloat(indexPath.row) * (itemWidth + space) let y : CGFloat = CGFloat(indexPath.section) + CGFloat(indexPath.section) * (itemHeight + space) attributes.frame = CGRect(x: x, y: y, width: itemWidth, height: itemHeight) return attributes } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let minRow : Int = (rect.origin.x > 0) ? Int(floor(rect.origin.x/(itemWidth + space))) : 0 let maxRow : Int = min(columns - 1, Int(ceil(rect.size.width / (itemWidth + space)) + CGFloat(minRow))) var attributes : Array<UICollectionViewLayoutAttributes> = [UICollectionViewLayoutAttributes]() for i in 0 ..< rows { for j in minRow ... maxRow { attributes.append(self.layoutAttributesForItem(at: IndexPath(item: j, section: i))!) } } return attributes } }

He estado luchando con esta tarea desde hace bastante tiempo. Lo que me gustaría desarrollar es una vista de desplazamiento o una vista de colección que se desplaza de forma continua tanto vertical como horizontal.

Aquí hay una imagen de cómo creo que debería ser esto. Los cuadros transparentes son las vistas / celdas que se vuelven a cargar desde la memoria. Tan pronto como una vista / celda salga de la pantalla, debe ser reutilizada para la próxima celda nueva ... tal como funciona un UITableViewController .

Sé que un UICollectionView solo se puede hacer para desplazamiento infinito horizontal O vertical, no ambos. Sin embargo, no sé cómo hacerlo utilizando un UIScrollView . Probé el código adjunto a una respuesta en esta pregunta y puedo hacer que vuelva a crear vistas (por ejemplo,% 20) pero eso no es realmente lo que necesito ... además, no es continuo.

Sé que es posible, porque la aplicación HBO Go hace esto ... Quiero exactamente la misma funcionalidad.

Mi pregunta: ¿Cómo puedo lograr mi objetivo? ¿Hay guías / tutoriales que me puedan mostrar cómo? No puedo encontrar ninguna.


La respuesta de @rdelmar funcionó a la perfección, pero necesitaba hacerlo de forma rápida. Aquí está la conversión :)

class NodeMap : UICollectionViewController { @IBOutlet var activateNodeButton : UIBarButtonItem? var rows = 10 var cols = 10 override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return rows } override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return cols } override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { return collectionView.dequeueReusableCellWithReuseIdentifier("node", forIndexPath: indexPath) } override func viewDidLoad() { self.collectionView!.collectionViewLayout = NodeLayout(itemWidth: 100.0, itemHeight: 100.0, space: 5.0) } } class NodeLayout : UICollectionViewFlowLayout { var itemWidth : CGFloat var itemHeight : CGFloat var space : CGFloat init(itemWidth: CGFloat, itemHeight: CGFloat, space: CGFloat) { self.itemWidth = itemWidth self.itemHeight = itemHeight self.space = space super.init() } required init(coder aDecoder: NSCoder) { self.itemWidth = 50 self.itemHeight = 50 self.space = 3 super.init() } override func collectionViewContentSize() -> CGSize { let w : CGFloat = CGFloat(self.collectionView!.numberOfItemsInSection(0)) * (itemWidth + space) let h : CGFloat = CGFloat(self.collectionView!.numberOfSections()) * (itemHeight + space) return CGSizeMake(w, h) } override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! { let attributes = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath) let x : CGFloat = CGFloat(indexPath.row) * (itemWidth + space) let y : CGFloat = CGFloat(indexPath.section) + CGFloat(indexPath.section) * (itemHeight + space) attributes.frame = CGRectMake(x, y, itemWidth, itemHeight) return attributes } override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? { let minRow : Int = (rect.origin.x > 0) ? Int(floor(rect.origin.x/(itemWidth + space))) : 0 let maxRow : Int = Int(floor(rect.size.width/(itemWidth + space)) + CGFloat(minRow)) var attributes : Array<UICollectionViewLayoutAttributes> = [UICollectionViewLayoutAttributes]() for i in 0...self.collectionView!.numberOfSections()-1 { for j in minRow...maxRow { attributes.append(self.layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: j, inSection: i))) } } return attributes } }


Puede obtener un desplazamiento infinito utilizando la técnica de volver a centrar el UIScrollView después de alejarse una cierta distancia del centro. Primero, debe hacer que el tamaño del contentSize suficientemente grande para que pueda desplazarse un poco, así que devuelvo 4 veces el número de elementos en mis secciones y 4 veces el número de secciones, y uso el operador mod en el método cellForItemAtIndexPath para obtener el derecho índice en mi matriz. A continuación, debe anular las layoutSubviews de layoutSubviews en una subclase de UICollectionView para realizar el recentrado (esto se demuestra en el video de la WWDC 2011, "Técnicas avanzadas de vista de desplazamiento"). Aquí está la clase de controlador que tiene la vista de colección (configurada en IB) como una subvista:

#import "ViewController.h" #import "MultpleLineLayout.h" #import "DataCell.h" @interface ViewController () @property (weak,nonatomic) IBOutlet UICollectionView *collectionView; @property (strong,nonatomic) NSArray *theData; @end @implementation ViewController - (void)viewDidLoad { self.theData = @[@[@"1",@"2",@"3",@"4",@"5"], @[@"6",@"7",@"8",@"9",@"10"],@[@"11",@"12",@"13",@"14",@"15"],@[@"16",@"17",@"18",@"19",@"20"]]; MultpleLineLayout *layout = [[MultpleLineLayout alloc] init]; self.collectionView.collectionViewLayout = layout; self.collectionView.showsHorizontalScrollIndicator = NO; self.collectionView.showsVerticalScrollIndicator = NO; layout.scrollDirection = UICollectionViewScrollDirectionHorizontal; self.view.backgroundColor = [UIColor blackColor]; [self.collectionView registerClass:[DataCell class] forCellWithReuseIdentifier:@"DataCell"]; [self.collectionView reloadData]; } - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section { return 20; } - (NSInteger)numberOfSectionsInCollectionView: (UICollectionView *)collectionView { return 16; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { DataCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"DataCell" forIndexPath:indexPath]; cell.label.text = self.theData[indexPath.section %4][indexPath.row %5]; return cell; } - (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath { // UICollectionViewCell *item = [collectionView cellForItemAtIndexPath:indexPath]; NSLog(@"%@",indexPath); }

Aquí está la subclase UICollectionViewFlowLayout :

#define space 5 #import "MultpleLineLayout.h" @implementation MultpleLineLayout { // a subclass of UICollectionViewFlowLayout NSInteger itemWidth; NSInteger itemHeight; } -(id)init { if (self = [super init]) { itemWidth = 60; itemHeight = 60; } return self; } -(CGSize)collectionViewContentSize { NSInteger xSize = [self.collectionView numberOfItemsInSection:0] * (itemWidth + space); // "space" is for spacing between cells. NSInteger ySize = [self.collectionView numberOfSections] * (itemHeight + space); return CGSizeMake(xSize, ySize); } - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path { UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path]; attributes.size = CGSizeMake(itemWidth,itemHeight); int xValue = itemWidth/2 + path.row * (itemWidth + space); int yValue = itemHeight + path.section * (itemHeight + space); attributes.center = CGPointMake(xValue, yValue); return attributes; } -(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect { NSInteger minRow = (rect.origin.x > 0)? rect.origin.x/(itemWidth + space) : 0; // need to check because bounce gives negative values for x. NSInteger maxRow = rect.size.width/(itemWidth + space) + minRow; NSMutableArray* attributes = [NSMutableArray array]; for(NSInteger i=0 ; i < self.collectionView.numberOfSections; i++) { for (NSInteger j=minRow ; j < maxRow; j++) { NSIndexPath* indexPath = [NSIndexPath indexPathForItem:j inSection:i]; [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; } } return attributes; }

Y finalmente, aquí está la subclase de UICollectionView :

-(void)layoutSubviews { [super layoutSubviews]; CGPoint currentOffset = self.contentOffset; CGFloat contentWidth = self.contentSize.width; CGFloat contentHeight = self.contentSize.height; CGFloat centerOffsetX = (contentWidth - self.bounds.size.width)/ 2.0; CGFloat centerOffsetY = (contentHeight - self.bounds.size.height)/ 2.0; CGFloat distanceFromCenterX = fabsf(currentOffset.x - centerOffsetX); CGFloat distanceFromCenterY = fabsf(currentOffset.y - centerOffsetY); if (distanceFromCenterX > contentWidth/4.0) { // this number of 4.0 is arbitrary self.contentOffset = CGPointMake(centerOffsetX, currentOffset.y); } if (distanceFromCenterY > contentHeight/4.0) { self.contentOffset = CGPointMake(currentOffset.x, centerOffsetY); } }


Restablecer el ContentOffset probablemente sea la mejor solución que se haya descubierto hasta ahora.

Se deben tomar algunos pasos para lograr esto:

  1. Rellene elementos adicionales en el lado izquierdo y derecho del conjunto de datos original para lograr un área de desplazamiento más grande; Esto es similar a tener un gran conjunto de datos duplicados, pero la diferencia es la cantidad;
  2. Al inicio, el contenido de la vista de colección se calcula para mostrar solo el conjunto de datos original (dibujado en rectángulos negros);
  3. Cuando el usuario se desplaza a la derecha y contentOffset alcanza el valor de activación, restablecemos contentOffset para mostrar los mismos resultados visuales; pero en realidad datos diferentes; Cuando el usuario se desplaza hacia la izquierda, se utiliza la misma lógica.

Por lo tanto, el trabajo pesado consiste en calcular cuántos elementos deben rellenarse tanto en el lado izquierdo como en el derecho. Si observa la ilustración, encontrará que debe rellenar un mínimo de una pantalla adicional de elementos a la izquierda y también otra pantalla adicional a la derecha. La cantidad exacta rellenada depende de la cantidad de elementos en el conjunto de datos original y del tamaño de su artículo.

Escribí un post sobre esta solución:

http://www.awsomejiang.com/2018/03/24/Infinite-Scrolling-and-the-Tiling-Logic/