简体   繁体   English

Swift UITableView 删除所选行及其上方的任何行

[英]Swift UITableView delete selected row and any rows above it

My TableView contains cells that represent a mileage log.我的 TableView 包含代表里程日志的单元格。 I need to allow users that ability to delete any mistakes.我需要让用户能够删除任何错误。 The tableview lists the logs in descending order. tableview 按降序列出日志。 Deleting the top row is OK.删除顶行就可以了。 Deleting any other row I need to issue a warning as an alert and if confirmed then delete the selected row + all rows above it.删除任何其他行,我需要发出警告作为警报,如果确认,则删除所选行及其上方的所有行。 Is this possible?这可能吗? Is there any example code anywhere?是否有任何示例代码?

UPDATE更新

Based on the two answers I have had so far I have done the following....根据我到目前为止的两个答案,我做了以下工作......

import UIKit
import CoreData

class MileageLogsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {

    @IBOutlet var milageLogTableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
            try fetchedResultsController.performFetch()
        } catch {
            let fetchError = error as NSError
            print("Unable to fetch MileageLog")
            print("\(fetchError), \(fetchError.localizedDescription)")
        }

        // Display an Edit button in the navigation bar for this view controller.
        self.navigationItem.leftBarButtonItem = self.editButtonItem()
    }

    // MARK: - Table view data source

    private lazy var fetchedResultsController: NSFetchedResultsController = {
        // Initialize Fetch Request
        let fetchRequest = NSFetchRequest(entityName: "MileageLog")

        // Add Sort Descriptors
        let dateSort = NSSortDescriptor(key: "tripDate", ascending: false)
        let mileSort = NSSortDescriptor(key: "startMileage", ascending: false)
        fetchRequest.sortDescriptors = [dateSort, mileSort]

        let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let managedObjectContext = delegate.managedObjectContext

        // Initialize Fetched Results Controller
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "rootCache")

        //ADDED AS PER ANSWER FROM SANDEEP
    fetchedResultsController.delegate = self

        return fetchedResultsController

    }()


    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        if let sections = fetchedResultsController.sections {
            return sections.count
        }

        return 0
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let sections = fetchedResultsController.sections {
            let sectionInfo = sections[section]
            return sectionInfo.numberOfObjects
        }

        return 0
    } 

     override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("MileageLogCell") as! MileageTableViewCell

        // Fetch MileageLog
        if let mileageLog = fetchedResultsController.objectAtIndexPath(indexPath) as? MileageLog {
            //format date as medium style date
            let formatter = NSDateFormatter()
            formatter.dateStyle = .MediumStyle
            let logDateString = formatter.stringFromDate(mileageLog.tripDate!)
            //format NSNumber mileage to string
            let mileageInt:NSNumber = mileageLog.startMileage!
            let mileageString = String(mileageInt)

            cell.lb_LogDate.text = logDateString
            cell.lb_LogMileage.text = mileageString
            cell.lb_LogStartLocation.text = mileageLog.startLocation
            cell.lb_LogDestination.text = mileageLog.endLocation
        }
        return cell
     }


    // Override to support conditional editing of the table view.
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        return true
    }

    // MARK: Fetched Results Controller Delegate Methods
    func controllerWillChangeContent(fetchedResultsController: NSFetchedResultsController) {
        tableView.beginUpdates()
    }

    func controllerDidChangeContent(fetchedResultsController: NSFetchedResultsController) {
        tableView.endUpdates()
    }

    func controller(fetchedResultsController: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch (type) {
            case .Insert:
                break;
            case .Delete:

                let context = fetchedResultsController.managedObjectContext
                if let indexPath = indexPath {

                    if indexPath.row == 0 {
                        //this is the top (first row)
                        // Deleting without warning
                        let objectToDelete = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
                        context.deleteObject(objectToDelete)

                        do {
                            try context.save()
                            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

                        } catch {
                            print(error)
                        }
                        self.tableView.reloadData();

                    } else {
                        //we are deleted a row that is not the top row
                        // we need to give a warning and if acknowledged then delele all rows from the selected row and all rows above it

                        let alertController = UIAlertController(title: nil, message: "Are you sure? This will remove this and all logs above it.", preferredStyle: .Alert)
                        let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in

                        }
                        alertController.addAction(cancelAction)
                        let deleteAction = UIAlertAction(title: "Delete", style: .Default) { (action) in

                            for deleteindex in 0 ... indexPath.row {
                                let deleteIndexPath = NSIndexPath(forRow: deleteindex, inSection: 0)
                                let objectToDelete = self.fetchedResultsController.objectAtIndexPath(deleteIndexPath) as! NSManagedObject
                                context.deleteObject(objectToDelete)

                                do {
                                    try context.save()
                                    self.tableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: .Fade)

                                } catch {
                                    print(error)
                                }
                            }
                            self.tableView.reloadData();
                        }
                        alertController.addAction(deleteAction)

                        // Dispatch on the main thread
                        dispatch_async(dispatch_get_main_queue()) { 
                            self.presentViewController(alertController, animated: true, completion:nil)
                        }

                    }
                }
                break;
            case .Update:
                break;
            case .Move:
                break;
        }
    }

}

Now my problem is that the touching Delete does nothing.现在我的问题是触摸删除什么也不做。 The treeview is correctly populated.树视图已正确填充。 The Edit button is in the navbar.编辑按钮位于导航栏中。 Click Edit and the 'no entry' icon appear on each row... slide a row and the Delete block appears.单击“编辑”,每行都会出现“无条目”图标...滑动一行,会出现“删除”块。 Click delete and nothing...!点击删除,什么都没有...! What have I missed out?我错过了什么?

FINAL WORKING FIX最终工作修复

// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {

    switch editingStyle {

     case .Delete:
        let context = fetchedResultsController.managedObjectContext
            if indexPath.row == 0 {
                //this is the top (first row)
                // Deleting without warning
                let indexPathToDelete = NSIndexPath(forRow: 0, inSection: 0)
                let objectToDelete = fetchedResultsController.objectAtIndexPath(indexPathToDelete) as! NSManagedObject
                context.deleteObject(objectToDelete)

                do {
                    try context.save()
                    //self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

                } catch {
                    print(error)
                }
                //self.tableView.reloadData();

            } else {
                //we are deleted a row that is not the top row
                // we need to give a warning and if acknowledged then delele all rows from the selected row and all rows above it

                let alertController = UIAlertController(title: nil, message: "Are you sure? This will remove this and all logs above it.", preferredStyle: .Alert)
                let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in

                }
                alertController.addAction(cancelAction)
                let deleteAction = UIAlertAction(title: "Delete", style: .Default) { (action) in

                    for deleteindex in 0 ... indexPath.row {
                        let deleteIndexPath = NSIndexPath(forRow: deleteindex, inSection: 0)
                        let objectToDelete = self.fetchedResultsController.objectAtIndexPath(deleteIndexPath) as! NSManagedObject
                        context.deleteObject(objectToDelete)

                    }

                    do {
                        try context.save()

                    } catch {
                        print(error)
                    }
                }
                alertController.addAction(deleteAction)

                // Dispatch on the main thread
                dispatch_async(dispatch_get_main_queue()) {
                    self.presentViewController(alertController, animated: true, completion:nil)
                }

            }
        break;

    default :
        return
    }

}

// MARK: Fetched Results Controller Delegate Methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
    tableView.beginUpdates()
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    tableView.endUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch type {
    case .Insert:
        break;
    case .Delete:
        tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
    case .Update:
        break;
    case .Move:
        break;
    }
}

Apart from the enhancement to use edit actions this is the easy solution.除了增强使用编辑操作之外,这是一个简单的解决方案。

First of all do not touch the delegate method didChangeObject .首先不要接触委托方法didChangeObject
Leave it as it is.保持原样。 It is called after making changes in the managed object context and works like the view in the MVC pattern.它在托管对象上下文中进行更改后调用,其工作方式类似于 MVC 模式中的视图。

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    switch type {
        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .fade)
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
        case .update:
            self.configureCell(tableView.cellForRow(at: indexPath!)!, atIndexPath: indexPath!)
        case .move:
            tableView.deleteRows(at: [indexPath!], with: .fade)
            tableView.insertRows(at: [newIndexPath!], with: .fade)
    }
}

Insert the code to delete the rows in commitEditingStyle which works like the model in the MVC pattern.插入代码以删除commitEditingStyle中的行,其工作方式类似于 MVC 模式中的模型。 The code deletes all rows from the selected row above in the current section.该代码从当前部分的上方选定行中删除所有行。

override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
    switch editingStyle {
    case .delete:
      let context = fetchedResultsController.managedObjectContext
      let section = indexPath.section
      let currentRow = indexPath.row
      for index in 0...currentRow {
          let indexPathToDelete = IndexPath(row: index, section: section)
          let objectToDelete = fetchedResultsController.object(at: indexPathToDelete) as! NSManagedObject
          context.delete(objectToDelete)
      }
      do {
        try context.save()
      } catch {
        print(error)
      }
      
    case .insert, .none: break
    }
  }

Here is what I believe what you can do :)这是我相信你能做的:)

Knowing that you have already sorted your data using fetchedResultsController sort predicate :) now all you have to do is to delete all the rows from 0 to row the user has selected :)知道您已经使用 fetchedResultsController 排序谓词对数据进行了排序:) 现在您要做的就是删除从 0 到用户选择的行的所有行:)

Because you have only one section and all cell will have section as "0" in their index path :) all you have to do is to iterate from 0 to row number of the cell selected :)因为您只有一个部分,并且所有单元格的索引路径中的部分都为“0”:) 您所要做的就是从 0 迭代到所选单元格的行号:)

create a indexpath with iterated value as row and "0" as section :) fetch the object from fetchedResultsController and delete it in a loop :)创建一个索引路径,迭代值作为行,“0”作为部分:) 从 fetchedResultsController 获取对象并在循环中删除它:)

Once done reload the tableView :)完成后重新加载 tableView :)

Refering to your previous question :)参考你之前的问题:)

case .Delete:
        // Delete the row from the data source

        let context = fetchedResultsController.managedObjectContext
        for deleteindex in 0 ... indexPath.row {
                var deleteIndexPath = NSIndexPath(forRow: deleteindex, inSection: 0)
                let objectToDelete = fetchedResultsController.objectAtIndexPath(deleteIndexPath) as! NSManagedObject
               context.deleteObject(objectToDelete)
        }
        do {
          try context.save()
          self.tableView.reloadData();
        } catch {
           print(error)
        }

EDIT编辑

As per your updated question, none of the fetchedResults controller delegates are getting called.根据您更新的问题,没有调用 fetchedResults 控制器委托。

That's because, you haven't told fetchedResults controller whom to inform when something happens in DB.那是因为,您还没有告诉 fetchedResults 控制器在 DB 中发生某些事情时通知谁。 What I meant is you have not set the delegate of fetchedResults controller to self buddy :)我的意思是你没有将 fetchedResults 控制器的委托设置为 self buddy :)

copy pasting your own code with modification and comment复制粘贴您自己的代码并进行修改和注释

private lazy var fetchedResultsController: NSFetchedResultsController = {
        // Initialize Fetch Request
        let fetchRequest = NSFetchRequest(entityName: "MileageLog")

        // Add Sort Descriptors
        let dateSort = NSSortDescriptor(key: "tripDate", ascending: false)
        let mileSort = NSSortDescriptor(key: "startMileage", ascending: false)
        fetchRequest.sortDescriptors = [dateSort, mileSort]

        let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let managedObjectContext = delegate.managedObjectContext

        // Initialize Fetched Results Controller
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "rootCache")

        //set the delegate to self here. You have confirmed the fetchedResultsController delegate in interface remember ??
        fetchedResultsController.delegate = self

        return fetchedResultsController

    }()

Hope I answerred your question :) Have a doubt ?希望我回答了你的问题 :) 有疑问吗? Leave a comment below :) Happy coding buddy :)在下面发表评论 :) 快乐的编码伙伴 :)

You can do it using the editActionsForRowAtIndexPath method of UITableViewDelegate protocol, available since iOS 8.0.您可以使用UITableViewDelegate协议的editActionsForRowAtIndexPath方法来完成,从 iOS 8.0 开始可用。

Return Value返回值

An array of UITableViewRowAction objects representing the actions for the row.表示行操作的 UITableViewRowAction 对象数组。 Each action you provide is used to create a button that the user can tap.您提供的每个操作都用于创建用户可以点击的按钮。

Discussion讨论

Use this method when you want to provide custom actions for one of your table rows.当您想为表格行之一提供自定义操作时,请使用此方法。 When the user swipes horizontally in a row, the table view moves the row content aside to reveal your actions.当用户在一行中水平滑动时,表格视图将行内容移到一边以显示您的操作。 Tapping one of the action buttons executes the handler block stored with the action object.点击其中一个动作按钮会执行与动作对象一起存储的处理程序块。

Here is an example of implementation这是一个实现的例子

func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
    
    if indexPath.row == 0 {
        // Deleting without warning

        // The closure will be called when tapping on the action
        let deleteClosure = { (action: UITableViewRowAction!, indexPath: NSIndexPath!) -> Void in    

            // Delete the first object
            // you can animate it with the BeginUpdates... EndUpdates too             
            self.dataSourceArray.removeAtIndex(0)
            self.tableView.reloadData()
        }
      
        // Default style is Destructive
        return [UITableViewRowAction(style: .Default, title: "Delete", handler: deleteClosure)]
    }
    else  {
        // Deleting with warning

        // The closure will be called when tapping on the action
        let deleteClosure = { (action: UITableViewRowAction!, indexPath: NSIndexPath!) -> Void in    

            let alertController = UIAlertController(title: nil, message: Localization(kStr_NotifDisabled), preferredStyle: .Alert)
            let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
                
            }
            alertController.addAction(cancelAction)
            let deleteAction = UIAlertAction(title: "Delete", style: .Default) { (action) in
                        
                self.dataSourceArray(Range(start: 0, end: indexPath.row))    
                self.tableView.reloadData()
            }
            alertController.addAction(deleteAction)
                   
            // Dispatch on the main thread
            dispatch_async(dispatch_get_main_queue()) { 
                self.presentViewController(alertController, animated: true, completion:nil)
            }
        }          
        // Default style is Destructive
        return [UITableViewRowAction(style: .Default, title: "Delete", handler: deleteClosure)]
    }            
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true

} }

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
  arrRecordList.removeAtIndex(indexPath.row)    
  tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)

}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM