Pasando a la izquierda para crear una nueva colección ViewCell

Estoy intentando implementar una manera más myCollectionView usar para agregar new collectionViewCells para myCollectionView (que solo muestra una celda a la vez). Quiero que sea algo así como cuando el usuario desliza hacia la izquierda y si el usuario está en la última celda, myCollectionView inserta una nueva celda cuando el usuario desliza para que el usuario pase "a la izquierda" la celda. Y también solo permito al usuario desplazarse por una celda a la vez.

EDIT: Entonces, creo que es un poco difícil describirlo en palabras, así que aquí hay un GIF para mostrar lo que quiero decir

introduzca la descripción de la imagen aquí

Así que en las últimas semanas he estado intentando implementar esto de varias maneras diferentes, y el que he tenido más éxito es usar el método de delegado scrollViewWillEndDragging y lo he implementado así:

  func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { // Getting the size of the cells let flowLayout = myCollectionView.collectionViewLayout as! UICollectionViewFlowLayout let cellWidth = flowLayout.itemSize.width let cellPadding = 10.0 as! CGFloat // Calculating which page "card" we should be on let currentOffset = scrollView.contentOffset.x - cellWidth/2 print("currentOffset is \(currentOffset)") let cardWidth = cellWidth + cellPadding var page = Int(round((currentOffset)/(cardWidth) + 1)) print("current page number is: \(page)") if (velocity.x < 0) { page -= 1 } if (velocity.x > 0) { page += 1 } print("Updated page number is: \(page)") print("Previous page number is: \(self.previousPage)") // Only allowing the user to scroll for one page! if(page > self.previousPage) { page = self.previousPage + 1 self.previousPage = page } else if (page == self.previousPage) { page = self.previousPage } else { page = self.previousPage - 1 self.previousPage = page } print("new page number is: " + String(page)) print("addedCards.count + 1 is: " + String(addedCards.count + 1)) if (page == addedCards.count) { print("reloading data") // Update data source addedCards.append("card") // Method 1 cardCollectionView.reloadData() // Method 2 // let newIndexPath = NSIndexPath(forItem: addedCards.count - 1, inSection: 0) // cardCollectionView.insertItemsAtIndexPaths([newIndexPath]) } // print("Centering on new cell") // Center the cardCollectionView on the new page let newOffset = CGFloat(page * Int((cellWidth + cellPadding))) print("newOffset is: \(newOffset)") targetContentOffset.memory.x = newOffset } 

Aunque creo que casi tengo el resultado deseado, todavía hay algunas preocupaciones y también errores que he encontrado.

Mi principal preocupación es el hecho de que, en lugar de insert una sola celda al final de myCollectionView vuelvo a cargar toda la tabla. La razón por la que hago esto es porque si no lo hice, myCollectionView.contentOffset no se cambiaría y, como resultado, cuando se crea una nueva celda, myCollectionView no se centra en la celda recién creada.

1. Si el usuario se desplaza muy lentamente y luego se detiene, se crea la nueva celda, pero luego myCollectionView se atasca entre dos celdas que no se centra en la celda recién creada.

2. Cuando myCollectionView está entre la segunda última celda y la última celda como resultado de 1. , la próxima vez que el usuario pase a la derecha, en lugar de crear una sola celda, se crean dos celdas.

También he usado diferentes forms de implementar este comportamiento, como usar scrollViewDidScroll y varios otros, pero sin éxito. ¿Alguien puede apuntarme en la dirección correcta, ya que estoy un poco perdido?

Aquí hay un enlace para download mi proyecto si quieres ver la interacción: My Example Project

Estos son los methods antiguos si te interesan:

Para hacer esto, he intentado 2 forms,

El primero es:

 func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { // page is the current cell that the user is on // addedCards is the array that the data source works with if (page == addedCards.count + 1) { let placeholderFlashCardProxy = FlashCardProxy(phrase: nil, pronunciation: nil, definition: nil) addedCards.append(placeholderFlashCardProxy) let newIndexPath = NSIndexPath(forItem: addedCards.count, inSection: 0) cardCollectionView.insertItemsAtIndexPaths([newIndexPath]) cardCollectionView.reloadData() } } 

El problema con el uso de este método es que:

  1. Algunas veces obtendré un locking como resultado de: NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0.
  2. Cuando se agrega una nueva collectionCell , a veces se mostrará la input que el usuario ha escrito desde la celda anterior (esto podría tener que ver con la eliminación de la capa y la reutilización de la celda, aunque, una vez más, no estoy seguro y estaría agradecido si alguien lo respondió)
  3. La inserción no es suave, quiero que el usuario pueda deslizar hacia la izquierda en la última celda y "ingresar" a una nueva celda. Como si estuviera actualmente en la última celda, deslizar hacia la izquierda me pondría automáticamente en la nueva celda, porque ahora cuando deslizo hacia la izquierda se crea una celda nueva que no se centra en la celda recién creada

El segundo método que estoy usando es:

 let swipeLeftGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipedLeftOnCell:") swipeLeftGestureRecognizer.direction = .Left myCollectionView.addGestureRecognizer(swipeLeftGestureRecognizer) swipeLeftGestureRecognizer.delegate = self 

Aunque el gesto de deslizar muy pocas veces responde, pero si uso un gesto de toque, myCollectionView siempre responde que es muy extraño (sé de nuevo que esta es una pregunta)

Mi pregunta es ¿cuál es la mejor manera de implementar lo que he descrito anteriormente? Y si ninguno es bueno con lo que debería trabajar para crear los resultados deseados, he intentado hacer esto durante dos días y me preguntaba si alguien podría señalarme en la dirección correcta. ¡Gracias!

Espero que esto ayude de alguna manera 🙂

ACTUALIZAR

Actualicé el código para arreglar el desplazamiento del problema en cualquier dirección. La información actualizada se puede encontrar aquí

Nueva key actualizada

Vieja esencia

Primero voy a definir un model para una tarjeta

 class Card { var someCardData : String? } 

A continuación, cree una celda de vista de colección, con una vista de tarjeta dentro de la cual aplicaremos la transformación a

 class CollectionViewCell : UICollectionViewCell { override init(frame: CGRect) { super.init(frame: frame) self.addSubview(cardView) self.addSubview(cardlabel) } override func prepareForReuse() { super.prepareForReuse() cardView.alpha = 1.0 cardView.layer.transform = CATransform3DIdentity } override func layoutSubviews() { super.layoutSubviews() cardView.frame = CGRectMake(contentPadding, contentPadding, contentView.bounds.width - (contentPadding * 2.0), contentView.bounds.height - (contentPadding * 2.0)) cardlabel.frame = cardView.frame } requinetworking init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } lazy var cardView : UIView = { [unowned self] in var view = UIView(frame: CGRectZero) view.backgroundColor = UIColor.whiteColor() return view }() lazy var cardlabel : UILabel = { [unowned self] in var label = UILabel(frame: CGRectZero) label.backgroundColor = UIColor.whiteColor() label.textAlignment = .Center return label }() } 

Luego configure el controller de vista con una vista de colección. Como verá, hay una class CustomCollectionView, que definiré cerca del final.

 class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource { var cards = [Card(), Card()] override func viewDidLoad() { super.viewDidLoad() view.addSubview(collectionView) collectionView.frame = CGRectMake(0, 0, self.view.bounds.width, tableViewHeight) collectionView.contentInset = UIEdgeInsetsMake(0, 0, 0, contentPadding) } lazy var collectionView : CollectionView = { [unowned self] in // MARK: Custom Flow Layout defined below var layout = CustomCollectionViewFlowLayout() layout.contentDelegate = self var collectionView = CollectionView(frame: CGRectZero, collectionViewLayout : layout) collectionView.clipsToBounds = true collectionView.showsVerticalScrollIndicator = false collectionView.registerClass(CollectionViewCell.self, forCellWithReuseIdentifier: "CollectionViewCell") collectionView.delegate = self collectionView.dataSource = self return collectionView }() // MARK: UICollectionViewDelegate, UICollectionViewDataSource func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { return 1 } func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cards.count } func collectionView(collectionView : UICollectionView, layout collectionViewLayout:UICollectionViewLayout, sizeForItemAtIndexPath indexPath:NSIndexPath) -> CGSize { return CGSizeMake(collectionView.bounds.width, tableViewHeight) } func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cellIdentifier = "CollectionViewCell" let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) as! CollectionViewCell cell.contentView.backgroundColor = UIColor.blueColor() // UPDATE If the cell is not the initial index, and is equal the to animating index // Prepare it's initial state if flowLayout.animatingIndex == indexPath.row && indexPath.row != 0{ cell.cardView.alpha = 0.0 cell.cardView.layer.transform = CATransform3DScale(CATransform3DIdentity, 0.0, 0.0, 0.0) } return cell } } 

ACTUALIZADO: ahora por la parte realmente complicada. Voy a definir CustomCollectionViewFlowLayout. La callback del protocolo devuelve el siguiente índice de inserción calculado por el layout del flujo

 protocol CollectionViewFlowLayoutDelegate : class { func flowLayout(flowLayout : CustomCollectionViewFlowLayout, insertIndex index : NSIndexPath) } /** * Custom FlowLayout * Tracks the currently visible index and updates the proposed content offset */ class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout { weak var contentDelegate: CollectionViewFlowLayoutDelegate? // Tracks the card to be animated // TODO: - Adjusted if cards are deleted by one if cards are deleted private var animatingIndex : Int = 0 // Tracks thje currently visible index private var visibleIndex : Int = 0 { didSet { if visibleIndex > oldValue { if visibleIndex > animatingIndex { // Only increment the animating index forward animatingIndex = visibleIndex } if visibleIndex + 1 > self.collectionView!.numberOfItemsInSection(0) - 1 { let currentEntryIndex = NSIndexPath(forRow: visibleIndex + 1, inSection: 0) contentDelegate?.flowLayout(self, insertIndex: currentEntryIndex) } } else if visibleIndex < oldValue && animatingIndex == oldValue { // if we start panning to the left, and the animating index is the old value // let set the animating index to the last card. animatingIndex = oldValue + 1 } } } override init() { super.init() self.minimumInteritemSpacing = 0.0 self.minimumLineSpacing = 0.0 self.scrollDirection = .Horizontal } requinetworking init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } // The width offset threshold percentage from 0 - 1 let thresholdOffsetPrecentage : CGFloat = 0.5 // This is the flick velocity threshold let velocityThreshold : CGFloat = 0.4 override func targetContentOffsetForProposedContentOffset(proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { let leftThreshold = CGFloat(collectionView!.bounds.size.width) * ((CGFloat(visibleIndex) - 0.5)) let rightThreshold = CGFloat(collectionView!.bounds.size.width) * ((CGFloat(visibleIndex) + 0.5)) let currentHorizontalOffset = collectionView!.contentOffset.x // If you either traverse far enought in either direction, // or flicked the scrollview over the horizontal velocity in either direction, // adjust the visible index accordingly if currentHorizontalOffset < leftThreshold || velocity.x < -velocityThreshold { visibleIndex = max(0 , (visibleIndex - 1)) } else if currentHorizontalOffset > rightThreshold || velocity.x > velocityThreshold { visibleIndex += 1 } var _proposedContentOffset = proposedContentOffset _proposedContentOffset.x = CGFloat(collectionView!.bounds.width) * CGFloat(visibleIndex) return _proposedContentOffset } } 

Y defina los methods de delegado en su controller de vista para insert una tarjeta nueva cuando el delegado le diga que necesita un nuevo índice

 extension ViewController : CollectionViewFlowLayoutDelegate { func flowLayout(flowLayout : CustomCollectionViewFlowLayout, insertIndex index : NSIndexPath) { cards.append(Card()) collectionView.performBatchUpdates({ self.collectionView.insertItemsAtIndexPaths([index]) }) { (complete) in } } 

Y a continuación se muestra la vista de Colección personalizada que aplica la animation mientras se desplaza por consiguiente 🙂

 class CollectionView : UICollectionView { override var contentOffset: CGPoint { didSet { if self.tracking { // When you are tracking the CustomCollectionViewFlowLayout does not update it's visible index until you let go // So you should be adjusting the second to last cell on the screen self.adjustTransitionForOffset(NSIndexPath(forRow: self.numberOfItemsInSection(0) - 1, inSection: 0)) } else { // Once the CollectionView is not tracking, the CustomCollectionViewFlowLayout calls // targetContentOffsetForProposedContentOffset(_:withScrollingVelocity:), and updates the visible index // by adding 1, thus we need to continue the trasition on the second the last cell self.adjustTransitionForOffset(NSIndexPath(forRow: self.numberOfItemsInSection(0) - 2, inSection: 0)) } } } /** This method applies the transform accordingly to the cell at a specified index - parameter atIndex: index of the cell to adjust */ func adjustTransitionForOffset(atIndex : NSIndexPath) { if let lastCell = self.cellForItemAtIndexPath(atIndex) as? CollectionViewCell { let progress = 1.0 - (lastCell.frame.minX - self.contentOffset.x) / lastCell.frame.width lastCell.cardView.alpha = progress lastCell.cardView.layer.transform = CATransform3DScale(CATransform3DIdentity, progress, progress, 0.0) } } } 

Creo que te perdiste un punto aquí en

let newIndexPath = NSIndexPath(forItem: addedCards.count, inSection: 0)

el indexPath debería ser

NSIndexPath(forItem : addedCards.count - 1, inSection : 0) no addedCards.count

Es por eso que estabas recibiendo el error.

 NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0