如何在不重复控制器代码的情况下在多个 ViewController 中使用 NSFetchedResultsControllerDelegate?

How do I use NSFetchedResultsControllerDelegate in multiple ViewController without repeating the controller code?

我正在制作一个使用 Core Data 的应用程序,碰巧在这个应用程序中我有三个 tableViews 填充了不同的数据并且它们都在不同的控制器中,以避免重复代码(因为它们完全相同) 我想对此有一个解决方案。

我已经尝试通过协议和继承自 UIViewController 的 MainController,后者又具有 NSFetchedResultsControllerDelegate 的扩展。 我几乎肯定做一个超级 class 我可以把这段代码放在那里是解决方案,但因为我是 swift 和编程的新手而且我也很肯定有些东西我不是做应该做的事。

在 parent class 我做了这样的事情:

class MainController: UIViewController, NSFetchedResultsController {

     var activeTableView: UITableView?
}

并且在 child:

class SecdController: MainController {

   @IBOutlet weak var tableView: UITableView!

   override var activeTableView: UITableView? {
     get {return tableView}
     set {}
  }
}

下面是我要调用三个控制器的代码

extension SomeController: NSFetchedResultsControllerDelegate {

    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: .top)
            break
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
            break
        case .update:
            tableView.reloadRows(at: [indexPath!], with: .top)
        case .move:
            tableView.reloadRows(at: [indexPath!], with: .top)
        default:
            fatalError("feature not yet implemented")
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {

        let indexSet = IndexSet(integer: sectionIndex)

        switch type {

        case .insert:
            tableView.insertSections(indexSet, with: .top)
        case .delete:
            tableView.deleteSections(indexSet, with: .top)
        case .update, .move:
            fatalError("Invalid change.")
        default:
            fatalError("feature not yet implemented")
        }
    }

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        tableView.beginUpdates()
    }
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        tableView.endUpdates()
    }
}

提前致谢:)

您的 MainController 应该处理 NSFetchedResultsControllerDelegate 而不是继承 NSFetchedResultsController


class MainController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    var fetchedResultsController: NSFetchedResultsController<NSManagedObject>?

override func viewDidLoad() {
        super.viewDidLoad()

        setupFetchedResultsController()

       fetchedResultsController?.delegate = self
       // perform fetch
    }

func setupFetchedResultsController() {
        preconditionFailure("The method must be overridden and initialize the fetch result controller.")
    }
}

extension MainController: NSFetchedResultsControllerDelegate {

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

// Do not force unwrap
        switch type {

        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .top)
            break
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
            break
        case .update:
            tableView.reloadRows(at: [indexPath!], with: .top)
        case .move:
            tableView.reloadRows(at: [indexPath!], with: .top)
        default:
            fatalError("feature not yet implemented")
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {

        let indexSet = IndexSet(integer: sectionIndex)

        switch type {

        case .insert:
            tableView.insertSections(indexSet, with: .top)
        case .delete:
            tableView.deleteSections(indexSet, with: .top)
        case .update, .move:
            fatalError("Invalid change.")
        default:
            fatalError("feature not yet implemented")
        }
    }

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        tableView.beginUpdates()
    }
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        tableView.endUpdates()
    }
}

然后在另一个视图控制器中覆盖 setupFetchedResultsController() 并设置获取结果控制器:

class SecondController: MainController {

   override func createFetchResultController() {

        let fetchRequest: NSFetchRequest<...> = ...
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]

        fetchedResultsController = ...
    }
}

这是一个棘手的问题,因为将委托重构为它自己的 class 的正常解决方案不起作用,因为 class 需要同时处理 table 委托(即是一个 UITableViewController),也是一个获取控制器委托。值得庆幸的是,有一个简单的解决方案,即使用一种称为组合的技术。将复制的 table 放入实现 NSFetchedResultsControllerDelegateUITableViewController subclass 并创建 fetchedResultsController 属性(如下图右侧所示) ).

对于不同的代码,将其放入几个 UIViewController 子 class 中(如下图左侧所示)。

使用嵌入 segue 使此视图控制器包含 table 控制器。在 viewDidLoad 中创建获取控制器并将其设置为 table 控制器。例如

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if([segue.identifier isEqualToString:@"embed"]){
        self.fetchedTableViewController = segue.destinationViewController;
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];

    self.fetchedTableViewController.fetchedResultsController = self.fetchedResultsController;

    // optional for more customization
    self.fetchedTableViewController.tableView.delegate = self;
    self.fetchedTableViewController.tableView.dataSource = self; 
}

如果你想处理一些特定的 table 或获取委托方法,那么一个很好的方法是将视图控制器设置为委托,然后将任何方法调用转发到嵌入式 table 控制器。例如

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.fetchedTableViewController controllerDidChangeContent:controller];

在这种情况下,您还应该实施 forwardingTargetForSelector 模式以确保其他所有内容也都被转发。

- (id)forwardingTargetForSelector:(SEL)aSelector{
    if(MHFProtocolHasInstanceMethod(@protocol(UITableViewDelegate), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return self.fetchedTableViewController;
        }
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(UITableViewDataSource), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return self.fetchedTableViewController;
        }
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(NSFetchedResultsControllerDelegate), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return self.fetchedTableViewController;
        }
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(UIDataSourceModelAssociation), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return self.fetchedTableViewController;
        }
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (BOOL)respondsToSelector:(SEL)aSelector{
    if([super respondsToSelector:aSelector]){
        return YES;
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(UITableViewDelegate), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return YES;
        }
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(UITableViewDataSource), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return YES;
        }
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(NSFetchedResultsControllerDelegate), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return YES;
        }
    }
    else if(MHFProtocolHasInstanceMethod(@protocol(UIDataSourceModelAssociation), aSelector)){
        if([self.fetchedTableViewController respondsToSelector:aSelector]){
            return YES;
        }
    }
    return NO;
}