这是使用NSMutableArray
作为后端存储,而非Core Data实现的NSFetchedResultsController
样式模式的实现。它提供了对存储进行操作和过滤的方法。
您通常将-dataSource
属性设置为您UITableView
或UICollectionView
的UAFilterableResultsController实例,并向它提供更改。像NSFetchedResultsController
一样,这个结果控制器将计算数据源中的差异,并通知您的UITableView
或UICollectionView
,从而使您能够动画化更改。
UAFilterableResultsController提供以下内容:
NSMutableArray
。UITableViewDataSource
实现。UICollectionViewDataSource
实现。NSPredicate
的过滤器。UAFilterableResultsController支持两种结构类型
这镜像了您在表或集合视图中使用典型的部分 -> 行/项设置。您可以直接操作对象,或整个部分。
如果您提供一个扁平的NSArray
作为数据源,UAFilterableResultsController会通知表或集合视图,该结构是一个包含NSArray
的行或项的单一部分。
UAFilterableResultsController是为与数据模型一起使用而设计的,因此最好有一个主键,但没有主键也能良好工作。
如果您提供一个主键,无论是通过-initWithPrimaryKeyPath:delegate:
还是-setPrimaryKeyPath:
,它将尝试使用指定主键路径中的值执行所有匹配、替换或合并操作。
假设您的数据看起来像这样:
- Employee
|-- employeeID
|-- firstName
`-- lastName
在设置初步数据源后,任何对-replaceObjects:
或-mergeObjects:
的调用都将寻找具有相同employeeID
的现有对象进行替换。
如果没有指定主键路径,则UAFilterableResultsController将在确定相等性时使用isEqual:
。
UAFilterableResultsController 需要 iOS6及以上系统。它已经在 iOS6 上进行了测试,但通常只在针对 iOS7 的生产应用中使用。在 OS X 上也能工作,但其实用性明显降低。
您可以选择使用 CocoaPods (搜索 UAFilterableResultsController) 进行安装,或者将其仓库克隆到您的项目中。
然后导入
#import <UAFilterableResultsController/UAFilterableResultsController.h>
通常情况下,您需要创建控制器的实例并将其作为数据源分配给您的表格或集合视图。
注意:由于 UITableView
和 UICollectionView
不保留其数据源,所以您需要保持对 UAFilterableResultsController 的强引用,通常作为您的 UITableViewController
或 UICollectionViewController
子类中的一个强属性。
不管怎样,当使用主键
[self setResultsController:[[UAFilterableResultsController alloc] initWithPrimaryKeyPath:@"imageID" delegate:self]];
[self.tableView setDataSource:self.resultsController];
或者不使用主键时
[self setResultsController:[[UAFilterableResultsController alloc] initWithDelegate:self]];
[self.collectionView setDataSource:self.resultsController];
根据您如何加载数据(是否使用 Storyboard,这是一个问题)您可能想要将其放在您的 -awakeFromNib
、-initWithStyle:
或 -initWithCollectionViewLayout:
实现中,因为在一个设置一个好的做法是在加载表格或集合视图之前设置 dataSource
。
UAFilterableResultsController 提供了一些方法来操作数据数组。以下是一些总结,但请查看完整的文档以了解更多。
使用供给的 data
设置(或替换)整个 NSMutableArray
的内容。如果有设置代理,它将通知代理来加载数据,或者根据需要动画化表格或集合视图中的更改。
将一个对象追加到数据堆栈中,作为最后一个部分中的最后一个对象,或作为唯一部分中的最后一个对象。如果有设置代理,则它将通知代理根据需要动画化表格或集合视图中的更改。
将指定对象从数据堆栈中删除。如果使用主键路径,则将删除具有匹配主键值的对象;否则,将删除使用 -isEqual:
进行匹配的对象。如果有设置代理,则它将通知代理根据需要动画化表格或集合视图中的更改。
用指定对象替换指定对象,这是非常有意义的。实际上,这主要用于您有主键路径的情况,因为它会定位具有匹配主键值的对象并将其替换。如果有设置代理,则它将通知代理根据需要动画化表格或集合视图中的更改。
值得注意的这些操作中大多数都有一个索引路径等效操作,例如 -removeObjectAtIndexPath:
和 -replaceObjectAtIndexPath:withObject:
。
如果需要可以合并更改。如果您多次遇到 API 端点并想要合并任何更改(比如说允许刷新),这非常有用。
您可以使用 -mergeObjects:
和 -mergeObjects:sortComparator:
来完成此操作。如果有设置代理,则它将通知代理根据需要动画化表格或集合视图中的更改。
如果您没有提供 NSSortComparator
,任何新对象将被追加到数据堆栈中。
除了操作单个对象外,您还可以操作整个节。
-addSection:
、-insertSection:atIndex:
、-removeSection:
、-replaceSection:withSection:
和 -replaceSectionAtIndex:withSection:
都是为了这个目的而存在的。和往常一样,如果设置了您的代理,它将被通知以适当的动画方式更改表或集合视图。
您可以批处理对对象或数据结构所做的更改。当您想开始更改时调用 -beginUpdates
,当您准备好提交批处理时调用 -endUpdates
。
当最外层的批处理结束时会向您的 UITableView 或 UICollectionView 发送更新批处理。您可以嵌套您的批处理。
在内部,对对象进行的所有更改(尤其是大规模合并或替换操作)都会批处理并提交进行同时动画。
UAFilterableResultsController 也可以支持搜索数据堆栈中的对象。您可以使用 -objectAtIndexPath:
或 -objectWithPrimaryKey:
定位已知的对象,或者使用 -indexPathOfObject:
或 -indexPathOfObjectWithPrimaryKey:
在数据堆栈中找到对象的定位。
您可以使用 -data
展示整个数据堆栈,或使用 -allObjects
获取堆栈中的所有对象。
筛选是 UAFilterableResultsController 的一个重要部分,这就是其名称的由来。您可以轻松提供多个基于 NSPredicate
的筛选器,并将它们自动应用到数据堆栈之上,在将其显示到表或集合视图之前。
UAFilter
对象是一个简单包装 NSPredicate
的包装器,允许命名和分组。将只应用属于同一组的 UAFilter
对象,任何具有相同组的新筛选器将覆盖现有筛选器。
您可以这样创建一个 UAFilter
:
UAFilter *filter = [UAFilter filterWithTitle:@"Micro instances"
group:@"Instance Types"
predicate:[NSPredicate predicateWithFormat:@"instanceType BEGINSWITH[c] \"t1\""]];
[self.resultsController addFilter:filter];
筛选器将应用于数据堆栈,您的代理会收到任何更改的行或项的任何更改通知,让您可以根据需要进行动画。
类似于对象,您可以使用 -addFilter:
、-addFilters:
和 -removeFilter:
使用操作数据堆栈中应用的筛选器。
您可以使用 -replaceFilters:
删除所有现有的筛选器并应用新的筛选器,或者使用 -clearFilters
删除所有筛选器。 -appliedFilters
将返回一个包含当前应用的筛选器的数组。
如果您正在使用 UISearchBar 并且想实时筛选结果,可以轻松完成。但请避免在此处使用 UISearchDisplayController
,因为我们并不总是与多个表或集合视图和谐相处。
相反,您可以将其 UISearchBar
连接到 UAFilterableResultsController 并使用其筛选功能进行操作。
// in your UISearchBarDelegate:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
UAFilter *filter = [UAFilter filterWithName:@"Search Results"
group:@"Search Results"
predicate:[NSPredicate predicateWithFormat:@"instanceName CONTAINS[c] %@", searchText]];
[self.resultsController addFilter:filter];
}
从那里,UAFilterableResultsController 将处理替换现有的“搜索结果”筛选器,计算筛选数据集之间的差异,并通知您的代理更改,以便您可以在表或集合视图中进行动画。
与NSFetchedResultsController类似,你可以依靠UAFilterableResultsController来帮助动画化你表格的变化。你还需要提供单元格、节标题和页脚,因为这些不能为你所计算。
确保你在UAFilterableResultsController实例上设置了-delegate
,然后配置你的代理(至少)提供单元格。
- (UITableViewCell *)filterableResultsController:(UAFilterableResultsController *)controller cellForItemWithObject:(id)object atIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"SomeIdentifier" forIndexPath:indexPath];
// make magic
return cell;
}
如果你想动画表格的变化,你需要寻找类似这样的办法
#pragma mark - UAFilterableResultsControllerDelegate Table Changes
- (void)filterableResultsControllerWillChangeContent:(UAFilterableResultsController *)controller
{
[self.tableView beginUpdates];
}
- (void)filterableResultsController:(UAFilterableResultsController *)controller didChangeSectionAtIndex:(NSInteger)sectionIndex forChangeType:(UAFilterableResultsChangeType)type
{
switch (type)
{
case UAFilterableResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:(NSUInteger)sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case UAFilterableResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:(NSUInteger)sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
default:
break;
}
}
- (void)filterableResultsController:(UAFilterableResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(UAFilterableResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
switch (type)
{
case UAFilterableResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case UAFilterableResultsChangeDelete:
// is the row deleted already selected?
if ([[self.tableView indexPathForSelectedRow] isEqual:indexPath])
[self.tableView deselectRowAtIndexPath:indexPath animated:NO];
[self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
break;
case UAFilterableResultsChangeMove:
[self.tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
break;
case UAFilterableResultsChangeUpdate:
// magics
break;
}
}
// when the table should be reloaded (used when moving to/from no data situations)
- (void)filterableResultsControllerShouldReload:(UAFilterableResultsController *)controller
{
[self.tableView reloadData];
}
// When the changes have been completed
- (void)filterableResultsControllerDidChangeContent:(UAFilterableResultsController *)controller
{
[self.tableView endUpdates];
}
与UICollectionView的工作和使用表格一样简单(是的,你还可以在集合视图中进行筛选/搜索)。
你总是需要提供单元格,以及可选的辅助视图,因为这些不能为你所计算。你可以选择动画变化,但与表格视图相比,这要复杂得多。
确保你在UAFilterableResultsController实例上设置了-delegate
属性,然后配置你的代理(至少)提供单元格。
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
DSEC2AddressCollectionViewCell *cell = [self.collectionView dequeueReusableCellWithReuseIdentifier:@"SomeIdentifier" forIndexPath:indexPath];
// make magic
return cell;
}
正如提到的,动画要复杂得多,因为你需要将这些包裹到单个调用中-performBatchUpdates:
。
我通常将它们收集到一个NSMutableArray
块中,然后一次性执行它们,如下所示
// a void block that accepts nothing and returns nothing
typedef void(^BatchUpdateBlock)();
@interface MyViewController ()
// Used to collect the updates for the collection view
@property (nonatomic, strong) NSMutableArray *batchUpdates;
@end
@implementation MyViewController
@synthesize batchUpdates=_batchUpdates;
// .. skipping all the irrelevant stuff
#pragma mark - UAFilterableResultsControllerDelegate Collection View Changes
- (void)filterableResultsControllerWillChangeContent:(UAFilterableResultsController *)controller
{
// start our batch updates array
if (self.batchUpdates == nil)
[self setBatchUpdates:[[NSMutableArray alloc] initWithCapacity:0]];
}
- (void)filterableResultsController:(UAFilterableResultsController *)controller didChangeSectionAtIndex:(NSInteger)sectionIndex forChangeType:(UAFilterableResultsChangeType)type
{
__block MyViewController *blockSelf = self;
BatchUpdateBlock block = NULL;
switch (type)
{
case UAFilterableResultsChangeInsert:
block = ^
{
[blockSelf.collectionView insertSections:[NSIndexSet indexSetWithIndex:(NSUInteger)sectionIndex]];
};
break;
case UAFilterableResultsChangeDelete:
block = ^
{
[blockSelf.collectionView deleteSections:[NSIndexSet indexSetWithIndex:(NSUInteger)sectionIndex]];
};
break;
default:
break;
}
if (block == NULL)
return;
if (self.batchUpdates != nil)
[self.batchUpdates addObject:block];
else
block();
}
- (void)filterableResultsController:(UAFilterableResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(UAFilterableResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
__block MyViewController *blockSelf = self;
BatchUpdateBlock block = NULL;
switch (type)
{
case UAFilterableResultsChangeInsert:
{
block = ^
{
[blockSelf.collectionView insertItemsAtIndexPaths:@[ newIndexPath ]];
};
break;
}
case UAFilterableResultsChangeDelete:
{
block = ^
{
[blockSelf.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
};
break;
}
case UAFilterableResultsChangeMove:
{
block = ^
{
[blockSelf.collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath];
};
break;
}
case UAFilterableResultsChangeUpdate:
{
block = ^
{
[blockSelf.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
};
break;
}
}
if (block == NULL)
return;
if (self.batchUpdates != nil)
[self.batchUpdates addObject:block];
else
block();
}
- (void)filterableResultsControllerShouldReload:(UAFilterableResultsController *)controller
{
[self.collectionView reloadData];
}
- (void)filterableResultsControllerDidChangeContent:(UAFilterableResultsController *)controller
{
// if we have batch changes, apply them all now
if (self.batchUpdates != nil)
{
[self.collectionView performBatchUpdates:^
{
for (BatchUpdateBlock block in blockSelf.batchUpdates)
block();
} completion:NULL];
}
[self setBatchUpdates:nil];
}
UAFilterableResultsController以开源形式提供,不提供保修和客户支持。我们尽力处理在GitHub上提出的问题。
你可以通过电子邮件将作者bok@<github用户名>.com联系。希望可以从该存储库的URL(https://github.com/unsignedapps/UAFilterableResultsController)中看到。
接受所有贡献并非常感激。我们会考虑所有提议的拉取请求,但请记住,有时如果你的需求是专业的,将其作为一个类别来实现可能更容易。
UAFilterableResultsController在MIT许可下发布。有关更多信息,请参阅LICENSE
文件。