UITableView 拖放到表外 = 崩溃

Posted

技术标签:

【中文标题】UITableView 拖放到表外 = 崩溃【英文标题】:UITableView Drag & Drop Outside Table = Crash 【发布时间】:2015-11-22 06:33:26 【问题描述】:

好人

我的拖放功能几乎可以完美运行。我长按一个单元格,它可以顺利地将按下的单元格移动到其他两个单元格之间的新位置。表格会调整,更改会保存到核心数据。太好了!

坏人

我的问题是,如果我将单元格拖到表格底部单元格下方,即使我不松开(取消按下)单元格...应用程序崩溃。如果我慢慢拖动,当单元格穿过最后一个单元格的 y 中心时,它真的会崩溃……所以我认为这是与快照获取位置有关的问题。不太重要但可能相关的是,如果我在最后一个包含值的单元格下方长按,它也会崩溃。

拖放运行一个 switch 语句,该语句根据状态运行三组代码之一:

新闻开始时的一个案例 单元格被拖动时的一种情况 当用户松开单元格时的一种情况

我的代码改编自本教程:

Drag & Drop Tutorial

我的代码:

 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.sharedApplication().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

潜在的罪魁祸首

我的猜测是,问题与单元格一旦低于最后一个单元格就无法获取坐标有关。它并不是真正的浮动,它不断地设置它相对于其他单元格的位置。我认为解决方案将是一个 if 语句,当一个位置没有单元格可参考时,它会做一些神奇的事情。但是什么!?!由于某种原因,为每个案例添加 nil 检查不起作用。

明确提出的问题

如何避免崩溃并处理拖动的单元格被拖到最后一个单元格下方的事件?

崩溃截图

【问题讨论】:

可能是它遇到“数组索引越界”的情况,认为单元格以某种方式放置在 tableview 中需要将新项目附加到 tableview 数组的位置不可能,所以系统被激怒并崩溃,具体是什么错误? @Larcerax 哈!好吧,我猜你是在正确的轨道上。它抛出一个 EXC:错误指令代码。控制台显示:“致命错误:数组索引超出范围 (lldb)”。知道如何使这种用户行为安全且不会导致崩溃吗? 是的,所以在事件发生之前,或者总是在拖动单元格时,您可能需要一些方法来解决打嗝,例如刷新排序或调度获取主队列事件,或者在时间等等之后调度,给一个短暂的中断。如果你可以在拖动开始时强制添加一个单元格,那么你很好,然后这个单元格被删除,这个单元格将是一个隐藏单元格,但这很混乱 @Larcerax 如果被拖动的单元格向南走得太远并且无法从单元格中获取位置,那么如果被拖动的单元格的位置等于最后一个单元格,那么 if 语句会怎么样?那样的话,如果用户试图拖到最后一个单元格的下方,那么当单元格到达底部时,它只会看起来像撞墙? 这可能会起作用,我见过一些类似的解决方案,这是一个非常有趣的问题。让我看看我能在这个上找到什么,因为我知道我自己很快就会遇到同样的问题。 【参考方案1】:

丑陋的

看来你只需要做一个先发制人的检查,以确保你的indexPath 不为零:

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

希望有帮助!

【讨论】:

移动整个 switch 语句还是只移动个别情况?我已经对每个案例中包含代码的快照进行了零检查。我刚刚添加了您的代码,告诉每个案例检查快照和 indexPath 是否不为零。不过,我得到:“致命错误:数组索引超出范围” 添加了我完整的 longPress 代码,这样你就可以看到我完整的和最近的尝试让它工作(包括你的代码!)。谢谢你第一次尝试,希望你是能解决这个问题的向导,因为它让我发疯,这就像我必须在应用准备上传之前修复剩下的 2 个错误中的 1 个! 此发帖者从未回答我的 cmets 但答案是“if indexPath != nil”语句需要紧跟 indexPath var 的声明,其余的拖放代码应该放在那个 if 语句。我对每个案例陈述进行了零检查,但这还不够。此外,我还有第二个问题导致了拖拽崩溃。我所有的单元格都是从 CoreData 生成的,除了我添加了最后一个占位符单元格。这导致“交换”函数失败,所以我将交换嵌套在 if 语句中,仅当 indexPath.row 小于我的 CoreData 项的计数时才运行。 很抱歉没有早点回复您的cmets!我很高兴你知道了!当您没有数据时,您是否也使用占位符单元格? 这个解决方案奏效了!但是我崩溃了:Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (9) must be equal to the number of rows contained in that section before the update (9), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 1 moved out).' 当将一个单元格移动到另一个 tableview 部分时。我该如何解决这个问题?【参考方案2】:

您没有说明崩溃发生在代码中的哪个位置,这使得确定发生了什么变得更加困难。在异常上设置断点以确定哪一行是罪魁祸首。为此,请使用 XCode 中断点列表左下角的“+”。

我认为主要问题是 indexPath。有几个问题:

    您正在使用 indexPath,即使它可能是 nil,在这一行中:

    let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as! CustomTableViewCell;
    

    indexPath 可能无效,即使它不为零。检查其部分和行成员是否不同于 NSNotFound。

最后,我一直在使用一个预制的、开源的 UITableView 子类,它可以为你完成所有的移动,所以你不必再自己实现它了。它还负责自动滚动,您甚至还没有考虑过。直接使用它,或将其用作代码的灵感: https://www.cocoacontrols.com/controls/fmmovetableview

【讨论】:

感谢您提出一些想法。我添加了崩溃的屏幕截图。立即查看您的建议

以上是关于UITableView 拖放到表外 = 崩溃的主要内容,如果未能解决你的问题,请参考以下文章

在后台删除的数据导致 UITableView 中的索引超出范围崩溃

在ios中从uitableviewcell拖放到uiimage视图

在 Swift 中从 TableView 拖放到另一个视图

在没有重新排序控制的情况下重新排序 UITableView

用部分动态填充分组的 uitableView

将 UIButton 放入 UITableView