如何在不重复控制器代码的情况下在多个 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 放入实现 NSFetchedResultsControllerDelegate
的 UITableViewController
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;
}
我正在制作一个使用 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 放入实现 NSFetchedResultsControllerDelegate
的 UITableViewController
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;
}