如何在多个 ViewController 中使用 NSFetchedResultsControllerDelegate 而不重复控制器代码?

Posted

技术标签:

【中文标题】如何在多个 ViewController 中使用 NSFetchedResultsControllerDelegate 而不重复控制器代码?【英文标题】:How do I use NSFetchedResultsControllerDelegate in multiple ViewController without repeating the controller code? 【发布时间】:2019-09-16 07:20:51 【问题描述】:

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

我尝试过通过协议和继承自 UIViewController 的 MainController,后者又具有 NSFetchedResultsControllerDelegate 的扩展。 我几乎肯定的是,我可以在其中放置此代码的超级类是解决方案,但由于我是 swift 和编程的新手,而且我也很肯定有些事情我没有按照预期的那样做。

在父类中我做了这样的事情:

class MainController: UIViewController, NSFetchedResultsController 

     var activeTableView: UITableView?

在孩子身上:

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

提前致谢:)

【问题讨论】:

【参考方案1】:

您的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 = ...
    

【讨论】:

先生,您救了我 :) 但是我必须为每个实体使用 fetchedResultsController,泛型类型需要更多时间,我没有的宝贵时间。编译器迫使我放弃大量的东西:(以及错误和错误【参考方案2】:

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

对于不同的代码,请针对每个特定情况将其放入多个UIViewController 子类(如下图左侧)。

使用嵌入转场使该视图控制器包含表控制器。在viewDidLoad 中创建获取控制器并将其设置为表控制器。例如

- (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; 

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

- (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;

【讨论】:

以上是关于如何在多个 ViewController 中使用 NSFetchedResultsControllerDelegate 而不重复控制器代码?的主要内容,如果未能解决你的问题,请参考以下文章

如何在一个 ViewController 中打开多个文件?

如何在多个 ViewController 中使用 NSFetchedResultsControllerDelegate 而不重复控制器代码?

如何在一个 viewController 中管理多个计时器?

在多个 viewController 中显示的通用或单个 uipickerview

如何在一个 viewController 上使用多个搜索栏来访问 Swift 中的不同 JSON 数据?

如何创建一个具有多个 ViewController 可以使用的关联视图类的 Nib