NSFetchedResultsController 喜欢 iOS 消息(最近的项目优先)
NSFetchedResultsController like iOS Messages (recent items first)
我想按照 iOS Messages 的工作方式设置 NSFetchedResultsController,这意味着,我想首先让最新的项目填满屏幕,然后在用户历史上向后滚动时获取table视图。
我认为我对仅使用 FetchedResultsController 有一点偏见,它是像 "normal" 这样的代表,我不太确定如何去做。
我也不确定这是否是我想要得到的东西的正确工具:)
我只想获取最新的记录,将它们显示在 table 视图中,并在用户向上滚动时继续获取项目并将它们插入现有行上方。
这只是我目前的常规设置:
import UIKit
import CoreData
class ViewController: UIViewController {
var coreDataStack: CoreDataStack!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var addButton: UIBarButtonItem!
var fetchedResultsController: NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let fetchRequest = NSFetchRequest(entityName: "Item")
let timestampSort = NSSortDescriptor(key: "timestamp", ascending: true)
fetchRequest.sortDescriptors = [timestampSort]
fetchedResultsController =
NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsController.delegate = self
do {
try self.fetchedResultsController.performFetch()
} catch let error as NSError {
print("initial fetch error is: \(error.localizedDescription)")
}
}
}
extension ViewController: UITableViewDataSource {
func numberOfSectionsInTableView
(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections!.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.name
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
let sectionInfo = self.fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier(
"ItemCell", forIndexPath: indexPath)
as! ItemCell
let item = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Item
cell.textLabel.text = item.name
return cell
}
}
extension ViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
case .Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
case .Update:
return
case .Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
}
NSFetchedResultsController
只是按排序顺序存储对象数组,您可以通过 fetchedObjects
方法访问它。因此,要显示最后 X 条消息,您需要显示该数组的最后 X 个元素。
与其尝试在每个 numberOfRowsInSection()
和 cellForRowAtIndexPath()
中计算它,我发现每次 [=15] 时缓存当前显示的 X 元素的副本更容易=] 变化(在 controllerDidChangeContent()
中)。也就是说,在对 controllerDidChangeContent
的每次调用中,您从获取的结果控制器的 fetchedObjects
复制最后 X 个元素
(Objective-C 中的示例代码,因为那是我在必须执行此操作的项目中使用的代码)
@property (strong, nonatomic) NSArray *msgsToDisplay;
@property unsigned long numToDisplay;
@property unsigned long numberActuallyDisplaying;
- (void)viewDidLoad {
// ...
self.msgsToDisplay = [[NSArray alloc] init];
self.numToDisplay = 20; // or whatever count you want to display initially
// ...
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
NSArray *allMsgs = [[_fetchedResultsController fetchedObjects] copy];
self.numberActuallyDisplaying = MIN(self.numToDisplay, [allMsgs count]);
self.msgsToDisplay = [allMsgs subarrayWithRange:NSMakeRange([allMsgs count] - self.numberActuallyDisplaying, self.numberActuallyDisplaying)];
}
那么您的行数(假设 table 中只有一个部分)就是您实际显示的消息数:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.numberActuallyDisplaying;
}
并且 cellForRowAtIndexPath
可以索引到对象的缓存副本中:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *msg = [self.msgsToDisplay objectAtIndex:indexPath.row];
//...
}
随着用户向上滚动,您可以使用 UIRefreshControl
(https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIRefreshControl_class/) 来允许用户请求更多数据。看起来您没有使用 UITableViewController
,因此您需要显式创建 UIRefreshControl
并将其添加到 table。在 viewDidLoad()
:
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView insertSubview:refreshControl atIndex:0];
当用户下拉刷新时,你可以将你的self.numToDisplay
设置为一个更大的数字,然后根据新的数字更新你的self.msgsToDisplay
和self.numActuallyDisplaying
来显示。
- (void) handleRefresh:(UIRefreshControl *)controller
{
self.numToDisplay += NUMBER_TO_DISPLAY_INCREMENT;
__block NSArray *allMsgs;
[[_fetchedResultsController managedObjectContext] performBlockAndWait:^{
allMsgs = [[_fetchedResultsController fetchedObjects] copy];
}];
self.numberActuallyDisplaying = MIN(self.numToDisplay, [allMsgs count]);
self.msgsToDisplay = [allMsgs subarrayWithRange:NSMakeRange([allMsgs count] - self.numberActuallyDisplaying, self.numberActuallyDisplaying)];
[controller endRefreshing];
}
将这一切转换为 Swift 应该很简单,但如果您需要这方面的帮助,请告诉我。
我想按照 iOS Messages 的工作方式设置 NSFetchedResultsController,这意味着,我想首先让最新的项目填满屏幕,然后在用户历史上向后滚动时获取table视图。
我认为我对仅使用 FetchedResultsController 有一点偏见,它是像 "normal" 这样的代表,我不太确定如何去做。
我也不确定这是否是我想要得到的东西的正确工具:)
我只想获取最新的记录,将它们显示在 table 视图中,并在用户向上滚动时继续获取项目并将它们插入现有行上方。
这只是我目前的常规设置:
import UIKit
import CoreData
class ViewController: UIViewController {
var coreDataStack: CoreDataStack!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var addButton: UIBarButtonItem!
var fetchedResultsController: NSFetchedResultsController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let fetchRequest = NSFetchRequest(entityName: "Item")
let timestampSort = NSSortDescriptor(key: "timestamp", ascending: true)
fetchRequest.sortDescriptors = [timestampSort]
fetchedResultsController =
NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
self.fetchedResultsController.delegate = self
do {
try self.fetchedResultsController.performFetch()
} catch let error as NSError {
print("initial fetch error is: \(error.localizedDescription)")
}
}
}
extension ViewController: UITableViewDataSource {
func numberOfSectionsInTableView
(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections!.count
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionInfo = fetchedResultsController.sections![section]
return sectionInfo.name
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
let sectionInfo = self.fetchedResultsController.sections![section]
return sectionInfo.numberOfObjects
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier(
"ItemCell", forIndexPath: indexPath)
as! ItemCell
let item = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Item
cell.textLabel.text = item.name
return cell
}
}
extension ViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
case .Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
case .Update:
return
case .Move:
self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Automatic)
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Automatic)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
}
NSFetchedResultsController
只是按排序顺序存储对象数组,您可以通过 fetchedObjects
方法访问它。因此,要显示最后 X 条消息,您需要显示该数组的最后 X 个元素。
与其尝试在每个 numberOfRowsInSection()
和 cellForRowAtIndexPath()
中计算它,我发现每次 [=15] 时缓存当前显示的 X 元素的副本更容易=] 变化(在 controllerDidChangeContent()
中)。也就是说,在对 controllerDidChangeContent
的每次调用中,您从获取的结果控制器的 fetchedObjects
(Objective-C 中的示例代码,因为那是我在必须执行此操作的项目中使用的代码)
@property (strong, nonatomic) NSArray *msgsToDisplay;
@property unsigned long numToDisplay;
@property unsigned long numberActuallyDisplaying;
- (void)viewDidLoad {
// ...
self.msgsToDisplay = [[NSArray alloc] init];
self.numToDisplay = 20; // or whatever count you want to display initially
// ...
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
NSArray *allMsgs = [[_fetchedResultsController fetchedObjects] copy];
self.numberActuallyDisplaying = MIN(self.numToDisplay, [allMsgs count]);
self.msgsToDisplay = [allMsgs subarrayWithRange:NSMakeRange([allMsgs count] - self.numberActuallyDisplaying, self.numberActuallyDisplaying)];
}
那么您的行数(假设 table 中只有一个部分)就是您实际显示的消息数:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.numberActuallyDisplaying;
}
并且 cellForRowAtIndexPath
可以索引到对象的缓存副本中:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Message *msg = [self.msgsToDisplay objectAtIndex:indexPath.row];
//...
}
随着用户向上滚动,您可以使用 UIRefreshControl
(https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIRefreshControl_class/) 来允许用户请求更多数据。看起来您没有使用 UITableViewController
,因此您需要显式创建 UIRefreshControl
并将其添加到 table。在 viewDidLoad()
:
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:@selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[self.tableView insertSubview:refreshControl atIndex:0];
当用户下拉刷新时,你可以将你的self.numToDisplay
设置为一个更大的数字,然后根据新的数字更新你的self.msgsToDisplay
和self.numActuallyDisplaying
来显示。
- (void) handleRefresh:(UIRefreshControl *)controller
{
self.numToDisplay += NUMBER_TO_DISPLAY_INCREMENT;
__block NSArray *allMsgs;
[[_fetchedResultsController managedObjectContext] performBlockAndWait:^{
allMsgs = [[_fetchedResultsController fetchedObjects] copy];
}];
self.numberActuallyDisplaying = MIN(self.numToDisplay, [allMsgs count]);
self.msgsToDisplay = [allMsgs subarrayWithRange:NSMakeRange([allMsgs count] - self.numberActuallyDisplaying, self.numberActuallyDisplaying)];
[controller endRefreshing];
}
将这一切转换为 Swift 应该很简单,但如果您需要这方面的帮助,请告诉我。