uicollectionviewcontroller tutorial horizontal example collection ios objective-c uiscrollview uicollectionview uicollectionviewlayout

ios - tutorial - Cómo crear un UICollectionView centrado como en el reproductor de Spotify



uicollectionview swift 4 tutorial (5)

Tengo muchas dificultades para crear un UICollectionView como en Spotify''s Player que actúa así:

El problema para mí es doble.

1) ¿Cómo centro las celdas para que pueda ver la celda central y la izquierda y la derecha?

  • Si creo celdas que son cuadradas y agrego espacio entre cada celda, las celdas se muestran correctamente pero no están centradas.

2) Con pagingEnabled = YES, la vista de colección se desplaza correctamente de una página a otra. Sin embargo, sin que las celdas estén centradas, simplemente mueve la vista de colección sobre una página que es el ancho de la pantalla. Entonces, la pregunta es cómo hacer que las páginas se muevan para obtener el efecto anterior.

3) ¿Cómo animas el tamaño de las celdas mientras se mueven?

  • No quiero preocuparme demasiado por esto. Si puedo hacer que eso funcione, sería genial, pero los problemas más difíciles son 1 y 2.

El código que tengo actualmente es un UICollectionView simple con configuración de delegado normal y celdas de UICollectionview personalizadas que son cuadrados. Tal vez necesito la subclase UICollectionViewFlowLayout? ¿O tal vez necesito convertir pagingEnabled en NO y luego usar eventos de deslizamiento personalizados? Me encantaría cualquier ayuda!


Bueno, hice UICollectionview moviéndose así, ayer.

Puedo compartir mi código contigo :)

Aquí está mi guión gráfico

asegúrese de desmarcar ''Paginación habilitada''

Aquí está mi código.

@interface FavoriteViewController () <UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout> { NSMutableArray * mList; CGSize cellSize; } @property (weak, nonatomic) IBOutlet UICollectionView *cv; @end @implementation FavoriteViewController - (void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; // to get a size. [self.view setNeedsLayout]; [self.view layoutIfNeeded]; CGRect screenFrame = [[UIScreen mainScreen] bounds]; CGFloat width = screenFrame.size.width*self.cv.frame.size.height/screenFrame.size.height; cellSize = CGSizeMake(width, self.cv.frame.size.height); // if cell''s height is exactly same with collection view''s height, you get an warning message. cellSize.height -= 1; [self.cv reloadData]; // setAlpha is for hiding looking-weird at first load [self.cv setAlpha:0]; } - (void) viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self scrollViewDidScroll:self.cv]; [self.cv setAlpha:1]; } #pragma mark - scrollview delegate - (void) scrollViewDidScroll:(UIScrollView *)scrollView { if(mList.count > 0) { const CGFloat centerX = self.cv.center.x; for(UICollectionViewCell * cell in [self.cv visibleCells]) { CGPoint pos = [cell convertPoint:CGPointZero toView:self.view]; pos.x += cellSize.width/2.0f; CGFloat distance = fabs(centerX - pos.x); // If you want to make side-cell''s scale bigger or smaller, // change the value of ''0.1f'' CGFloat scale = 1.0f - (distance/centerX)*0.1f; [cell setTransform:CGAffineTransformMakeScale(scale, scale)]; } } } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { // for custom paging CGFloat movingX = velocity.x * scrollView.frame.size.width; CGFloat newOffsetX = scrollView.contentOffset.x + movingX; if(newOffsetX < 0) { newOffsetX = 0; } else if(newOffsetX > cellSize.width * (mList.count-1)) { newOffsetX = cellSize.width * (mList.count-1); } else { NSUInteger newPage = newOffsetX/cellSize.width + ((int)newOffsetX%(int)cellSize.width > cellSize.width/2.0f ? 1 : 0); newOffsetX = newPage*cellSize.width; } targetContentOffset->x = newOffsetX; } #pragma mark - collectionview delegate - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { return mList.count; } - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { UICollectionViewCell * cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"list" forIndexPath:indexPath]; NSDictionary * dic = mList[indexPath.row]; UIImageView * iv = (UIImageView *)[cell.contentView viewWithTag:1]; UIImage * img = [UIImage imageWithData:[dic objectForKey:kKeyImg]]; [iv setImage:img]; return cell; } - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath { return cellSize; } - (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section { CGFloat gap = (self.cv.frame.size.width - cellSize.width)/2.0f; return UIEdgeInsetsMake(0, gap, 0, gap); } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section { return 0; } - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section { return 0; }

El código clave de make cell centrado es

  1. scrollViewWillEndDragging

  2. insetForSectionAtIndex

El código clave para animar el tamaño es

  1. scrollviewDidScroll

Deseo que esto te ayude

PD: Si desea cambiar alfa como la imagen que cargó, agregue [cell setalpha] en scrollViewDidScroll


Como ha dicho en el comentario que desea que en el código de Objective-c, hay una biblioteca muy famosa llamada iCarousel que puede ser útil para completar su requisito. Enlace: https://github.com/nicklockwood/iCarousel

Puede usar ''Rotary'' o ''Linear'' o algún otro estilo con poca o ninguna modificación para implementar la vista personalizada

Para implementarlo, solo ha implementado algunos métodos de delegado y está funcionando para ej:

//specify the type you want to use in viewDidLoad _carousel.type = iCarouselTypeRotary; //Set the following delegate methods - (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel { //return the total number of items in the carousel return [_items count]; } - (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view { UILabel *label = nil; //create new view if no view is available for recycling if (view == nil) { //don''t do anything specific to the index within //this `if (view == nil) {...}` statement because the view will be //recycled and used with other index values later view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 200.0f)]; ((UIImageView *)view).image = [UIImage imageNamed:@"page.png"]; view.contentMode = UIViewContentModeCenter; label = [[UILabel alloc] initWithFrame:view.bounds]; label.backgroundColor = [UIColor clearColor]; label.textAlignment = NSTextAlignmentCenter; label.font = [label.font fontWithSize:50]; label.tag = 1; [view addSubview:label]; } else { //get a reference to the label in the recycled view label = (UILabel *)[view viewWithTag:1]; } //set item label label.text = [_items[index] stringValue]; return view; } - (CGFloat)carousel:(iCarousel *)carousel valueForOption:(iCarouselOption)option withDefault:(CGFloat)value { if (option == iCarouselOptionSpacing) { return value * 1.1; } return value; }

Puede consultar la demostración completa en funcionamiento desde '' Ejemplos / Ejemplo básico de iOS '' que se incluye con el enlace del repositorio de Github

Como es antiguo y popular, puede encontrar algunos tutoriales relacionados y también será mucho más estable que la implementación de código personalizado.


Para crear un diseño de carrusel horizontal, tendrá que subclase UICollectionViewFlowLayout luego invalidar targetContentOffset(forProposedContentOffset:withScrollingVelocity:) , layoutAttributesForElements(in:) y shouldInvalidateLayout(forBoundsChange:) .

El siguiente código completo de Swift 4.1 - iOS 11 muestra cómo implementarlos.

ColecciónViewController.swift

import UIKit class CollectionViewController: UICollectionViewController { let collectionDataSource = CollectionDataSource() let flowLayout = ZoomAndSnapFlowLayout() override func viewDidLoad() { super.viewDidLoad() title = "Zoomed & snapped cells" guard let collectionView = collectionView else { fatalError() } //collectionView.decelerationRate = .fast // uncomment if necessary collectionView.dataSource = collectionDataSource collectionView.collectionViewLayout = flowLayout collectionView.register(CollectionViewCell.self, forCellWithReuseIdentifier: "Cell") } }

ZoomAndSnapFlowLayout.swift

import UIKit class ZoomAndSnapFlowLayout: UICollectionViewFlowLayout { let activeDistance: CGFloat = 200 let zoomFactor: CGFloat = 0.3 override init() { super.init() scrollDirection = .horizontal minimumLineSpacing = 40 itemSize = CGSize(width: 150, height: 150) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepare() { guard let collectionView = collectionView else { fatalError() } let verticalInsets = (collectionView.frame.height - collectionView.adjustedContentInset.top - collectionView.adjustedContentInset.bottom - itemSize.height) / 2 let horizontalInsets = (collectionView.frame.width - collectionView.adjustedContentInset.right - collectionView.adjustedContentInset.left - itemSize.width) / 2 sectionInset = UIEdgeInsets(top: verticalInsets, left: horizontalInsets, bottom: verticalInsets, right: horizontalInsets) super.prepare() } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let collectionView = collectionView else { return nil } let rectAttributes = super.layoutAttributesForElements(in: rect)!.map { $0.copy() as! UICollectionViewLayoutAttributes } let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.frame.size) // Make the cells be zoomed when they reach the center of the screen for attributes in rectAttributes where attributes.frame.intersects(visibleRect) { let distance = visibleRect.midX - attributes.center.x let normalizedDistance = distance / activeDistance if distance.magnitude < activeDistance { let zoom = 1 + zoomFactor * (1 - normalizedDistance.magnitude) attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1) attributes.zIndex = Int(zoom.rounded()) } } return rectAttributes } override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { guard let collectionView = collectionView else { return .zero } // Add some snapping behaviour so that the zoomed cell is always centered let targetRect = CGRect(x: proposedContentOffset.x, y: 0, width: collectionView.frame.width, height: collectionView.frame.height) guard let rectAttributes = super.layoutAttributesForElements(in: targetRect) else { return .zero } var offsetAdjustment = CGFloat.greatestFiniteMagnitude let horizontalCenter = proposedContentOffset.x + collectionView.frame.width / 2 for layoutAttributes in rectAttributes { let itemHorizontalCenter = layoutAttributes.center.x if (itemHorizontalCenter - horizontalCenter).magnitude < offsetAdjustment.magnitude { offsetAdjustment = itemHorizontalCenter - horizontalCenter } } return CGPoint(x: proposedContentOffset.x + offsetAdjustment, y: proposedContentOffset.y) } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { // Invalidate layout so that every cell get a chance to be zoomed when it reaches the center of the screen return true } override func invalidationContext(forBoundsChange newBounds: CGRect) -> UICollectionViewLayoutInvalidationContext { let context = super.invalidationContext(forBoundsChange: newBounds) as! UICollectionViewFlowLayoutInvalidationContext context.invalidateFlowLayoutDelegateMetrics = newBounds.size != collectionView?.bounds.size return context } }

CollectionDataSource.swift

import UIKit class CollectionDataSource: NSObject, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 9 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! CollectionViewCell return cell } }

ColecciónViewCell.swift

import UIKit class CollectionViewCell: UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .green } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }

Resultado Esperado:

Fuente:


Quería un comportamiento similar hace un tiempo, y con la ayuda de @Mike_M pude resolverlo. Aunque hay muchas, muchas maneras de hacer esto, esta implementación en particular es crear un UICollectionViewLayout personalizado.

Código a continuación (gist se puede encontrar aquí: https://gist.github.com/mmick66/9812223 )

Ahora es importante establecer lo siguiente: *yourCollectionView*.decelerationRate = UIScrollViewDecelerationRateFast , esto evita que las celdas se omitan con un golpe rápido.

Eso debería cubrir las partes 1 y 2. Ahora, para la parte 3, puede incorporar eso en la colección personalizada, mediante la invalidación y actualización constante, pero es un poco complicado si me pregunta. Por lo tanto, otro enfoque sería establecer un CGAffineTransformMakeScale( , ) en el UIScrollViewDidScroll donde se actualiza dinámicamente el tamaño de la celda en función de su distancia desde el centro de la pantalla.

Puede obtener las indexPaths de las celdas visibles de collectionView utilizando [*youCollectionView indexPathsForVisibleItems] y luego obtener las celdas para estas indexPaths. Para cada celda, calcule la distancia de su centro al centro de suCollectionView

El centro de collectionView se puede encontrar utilizando este ingenioso método: CGPoint point = [self.view convertPoint:*yourCollectionView*.center toView:*yourCollectionView];

Ahora establezca una regla, que si el centro de la celda está más alejado que x, el tamaño de la celda es, por ejemplo, el "tamaño normal", llámelo 1. y cuanto más se acerque al centro, más se acercará al doble. El tamaño normal 2.

entonces puedes usar la siguiente idea si / else:

if (distance > x) { cell.transform = CGAffineTransformMakeScale(1.0f, 1.0f); } else if (distance <= x) { float scale = MIN(distance/x) * 2.0f; cell.transform = CGAffineTransformMakeScale(scale, scale); }

Lo que sucede es que el tamaño de la celda seguirá exactamente tu toque. Déjame saber si tienes más preguntas ya que estoy escribiendo la mayor parte de esto de la cabeza.

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)offset withScrollingVelocity:(CGPoint)velocity { CGRect cvBounds = self.collectionView.bounds; CGFloat halfWidth = cvBounds.size.width * 0.5f; CGFloat proposedContentOffsetCenterX = offset.x + halfWidth; NSArray* attributesArray = [self layoutAttributesForElementsInRect:cvBounds]; UICollectionViewLayoutAttributes* candidateAttributes; for (UICollectionViewLayoutAttributes* attributes in attributesArray) { // == Skip comparison with non-cell items (headers and footers) == // if (attributes.representedElementCategory != UICollectionElementCategoryCell) { continue; } // == First time in the loop == // if(!candidateAttributes) { candidateAttributes = attributes; continue; } if (fabsf(attributes.center.x - proposedContentOffsetCenterX) < fabsf(candidateAttributes.center.x - proposedContentOffsetCenterX)) { candidateAttributes = attributes; } } return CGPointMake(candidateAttributes.center.x - halfWidth, offset.y); }


pagingEnabled no debe habilitarse, ya que necesita que cada celda tenga el ancho de la vista, lo que no funcionará para usted, ya que necesita ver los bordes de otras celdas. Para sus puntos 1 y 2. Creo que encontrará lo que necesita here de una de mis últimas respuestas a otra pregunta.

La animación de los tamaños de celda se puede lograr subclasificando UIcollectionviewFlowLayout y anulando layoutAttributesForItemAtIndexPath: Dentro de eso, modifique los atributos de diseño proporcionados por la primera llamada super y luego modifique el tamaño de los atributos de diseño según la posición relacionada con el centro de la ventana.

Esperemos que esto ayude.