UITableView Drag & Drop Outside Table = Crash

El bueno

Mi function de drag and drop casi funciona maravillosamente. Mantengo presionada una celda y me permite mover la celda presionada a una nueva location entre otras dos celdas. La tabla se ajusta y los cambios se guardan en los datos centrales. ¡Estupendo!

El malo

Mi problema es que si arrastro la celda debajo de la celda inferior de la tabla, incluso si no la suelto (no presione) de la celda … la aplicación se bloquea. Si hago el arrastre lentamente, realmente se bloquea cuando la celda cruza el centro y de la última celda … así que creo que es un problema relacionado con la captura de una location. Menos importante, pero posiblemente relacionado, es que si presiono por debajo de la última celda con un valor en él, también se bloquea.

El arrastre / suelta se ejecuta en una instrucción de conmutación que ejecuta uno de los tres sets de códigos según el estado:

  • Un caso cuando comienza la prensa
  • Un caso cuando la celda se está arrastrando
  • Un caso cuando cuando el usuario suelta la celda

Mi código está adaptado de este tutorial:

Drag & Drop Tutorial

Mi código:

func longPressGestureRecognized(gestureRecognizer: UIGestureRecognizer) { let longPress = gestureRecognizer as! UILongPressGestureRecognizer let state = longPress.state var locationInView = longPress.locationInView(tableView) var indexPath = tableView.indexPathForRowAtPoint(locationInView) struct My { static var cellSnapshot : UIView? = nil } struct Path { static var initialIndexPath : NSIndexPath? = nil } let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! CustomTableViewCell; var dragCellName = currentCell.nameLabel!.text var dragCellDesc = currentCell.descLabel.text //Steps to take a cell snapshot. Function to be called in switch statement func snapshotOfCell(inputView: UIView) -> UIView { UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0) inputView.layer.renderInContext(UIGraphicsGetCurrentContext()) let image = UIGraphicsGetImageFromCurrentImageContext() as UIImage UIGraphicsEndImageContext() let cellSnapshot : UIView = UIImageView(image: image) cellSnapshot.layer.masksToBounds = false cellSnapshot.layer.cornerRadius = 0.0 cellSnapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0) cellSnapshot.layer.shadowRadius = 5.0 cellSnapshot.layer.shadowOpacity = 0.4 return cellSnapshot } switch state { case UIGestureRecognizerState.Began: //Calls above function to take snapshot of held cell, animate pop out //Run when a long-press gesture begins on a cell if indexPath != nil && indexPath != nil { Path.initialIndexPath = indexPath let cell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell! My.cellSnapshot = snapshotOfCell(cell) var center = cell.center My.cellSnapshot!.center = center My.cellSnapshot!.alpha = 0.0 tableView.addSubview(My.cellSnapshot!) UIView.animateWithDuration(0.25, animations: { () -> Void in center.y = locationInView.y My.cellSnapshot!.center = center My.cellSnapshot!.transform = CGAffineTransformMakeScale(1.05, 1.05) My.cellSnapshot!.alpha = 0.98 cell.alpha = 0.0 }, completion: { (finished) -> Void in if finished { cell.hidden = true } }) } case UIGestureRecognizerState.Changed: if My.cellSnapshot != nil && indexPath != nil { //Runs when the user "lets go" of the cell //Sets CG Y-Coordinate of snapshot cell to center of current location in table (snaps into place) var center = My.cellSnapshot!.center center.y = locationInView.y My.cellSnapshot!.center = center var appDel: AppDelegate = (UIApplication.shanetworkingApplication().delegate as! AppDelegate) var context: NSManagedObjectContext = appDel.managedObjectContext! var fetchRequest = NSFetchRequest(entityName: currentListEntity) let sortDescriptor = NSSortDescriptor(key: "displayOrder", ascending: true ) fetchRequest.sortDescriptors = [ sortDescriptor ] //If the indexPath is not 0 AND is not the same as it began (didn't move)... //Update array and table row order if ((indexPath != nil) && (indexPath != Path.initialIndexPath)) { swap(&taskList_Cntxt[indexPath!.row], &taskList_Cntxt[Path.initialIndexPath!.row]) tableView.moveRowAtIndexPath(Path.initialIndexPath!, toIndexPath: indexPath!) toolBox.updateDisplayOrder() context.save(nil) Path.initialIndexPath = indexPath } } default: if My.cellSnapshot != nil && indexPath != nil { //Runs continuously while a long press is recognized (I think) //Animates cell movement //Completion block: //Removes snapshot of cell, cleans everything up let cell = tableView.cellForRowAtIndexPath(Path.initialIndexPath!) as UITableViewCell! cell.hidden = false cell.alpha = 0.0 UIView.animateWithDuration(0.25, animations: { () -> Void in My.cellSnapshot!.center = cell.center My.cellSnapshot!.transform = CGAffineTransformIdentity My.cellSnapshot!.alpha = 0.0 cell.alpha = 1.0 }, completion: { (finished) -> Void in if finished { Path.initialIndexPath = nil My.cellSnapshot!.removeFromSuperview() My.cellSnapshot = nil } })//End of competion block & end of animation }//End of 'if nil' }//End of switch }//End of longPressGestureRecognized 

Posible culpable

Mi conjetura es que el problema está relacionado con que la celda no puede get coorderadas una vez que está por debajo de la última celda. No es realmente flotante, constantemente establece su location en relación con las otras celdas. Creo que la solución será una instrucción if que hace algo mágico cuando no hay una celda para reference para una location. ¡¿¡Pero que!?! Agregar un cheque nulo a cada caso no funciona por alguna razón.

Pregunta claramente declarada

¿Cómo evito lockings y manejo un evento donde mi celda arrastrada se arrastra debajo de la última celda?

Captura de pantalla de crash :

Fuera de rango

El feo

Parece que simplemente tiene que hacer una comprobación preventiva, para asegurarse de que su indexPath no sea nula:

 var indexPath = tableView.indexPathForRowAtPoint(locationInView) if (indexPath != nil) { //Move your code to this block } 

¡Espero que ayude!

No indica dónde se produce el locking en el código, lo que dificulta determinar qué está sucediendo. Establezca un punto de interrupción en las excepciones para determinar qué línea es la culpable. Para hacerlo, use el '+' en la esquina inferior izquierda de la list de puntos de interrupción en XCode.

El problema principal que creo es con el indexPath. Hay un par de problemas:

  1. Está usando indexPath aunque puede ser nil, en esta línea:

     let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! CustomTableViewCell; 
  2. El indexPath puede ser no válido, aunque no sea nulo. Busque su sección y los miembros de la fila para que sean diferentes de NSNotFound.

Finalmente, he estado usando una subclass UITableView, de fuente abierta y pre-fabricada, que hace todo lo que se mueve por usted, por lo que ya no tiene que implementarlo usted mismo. También se encarga del autocontrol, que aún no ha considerado. Úselo directamente o utilícelo como inspiración para su código: https://www.cocoacontrols.com/controls/fmmovetableview