Ios 使用NSFetchedResultsController和UITableView(Swift)限制显示结果

Ios 使用NSFetchedResultsController和UITableView(Swift)限制显示结果,ios,uitableview,swift,core-data,nsfetchedresultscontroller,Ios,Uitableview,Swift,Core Data,Nsfetchedresultscontroller,现在我已经为此挣扎了两周,没有找到足够的教程,所以通过我自己的许多失败的调查,我觉得最好在这里问一下,看看是否可以提供一个好的答案,也许可以节省我自己和许多其他人很多时间和头痛 许多指南建议使用NSFetchedResultsController从核心数据存储中检索数据,特别是如果您希望在UITableView中显示数据。然而,几乎所有的教程都假设您希望一次显示所有数据,然后到此为止。例如,当我试图限制单元格的数量并实现“加载更多”功能时,所有这些都开始在接缝处崩溃 在以下代码中(在Swift中

现在我已经为此挣扎了两周,没有找到足够的教程,所以通过我自己的许多失败的调查,我觉得最好在这里问一下,看看是否可以提供一个好的答案,也许可以节省我自己和许多其他人很多时间和头痛

许多指南建议使用NSFetchedResultsController从核心数据存储中检索数据,特别是如果您希望在UITableView中显示数据。然而,几乎所有的教程都假设您希望一次显示所有数据,然后到此为止。例如,当我试图限制单元格的数量并实现“加载更多”功能时,所有这些都开始在接缝处崩溃

在以下代码中(在Swift中实现),使用AFNetworking从API检索数据并保存到核心数据。数据的保存发生在AF调用的成功块中

所有这些都有效,但我无法找到一种成功的方法来限制显示的项目/单元格的数量,并在用户向下滚动时增加它

import UIKit
import CoreData

class SearchResultsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, CLLocationManagerDelegate, NSFetchedResultsControllerDelegate{

@IBOutlet var tableView: UITableView!

// TableView Properties
private let cellIdentifier = "SearchResultCellIdentifier"
var refreshController = UIRefreshControl()

// persistant data stores and controllers
private var managedObjectContext : NSManagedObjectContext?

private var displayCount = 5

// Setup the fetch results controller
var fetchedResultsController: NSFetchedResultsController
{
    if _fetchedResultsController != nil
    {
        return _fetchedResultsController!
    }

    let fetchRequest = NSFetchRequest()
    // Edit the entity name as appropriate.
    let entity = NSEntityDescription.entityForName("Entity", inManagedObjectContext: self.managedObjectContext!)
    fetchRequest.entity = entity

    fetchRequest.fetchBatchSize = 25
    fetchRequest.fetchLimit = 25

    let sortDescriptor = NSSortDescriptor(key: "name", ascending: false)
    fetchRequest.sortDescriptors = [sortDescriptor]

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
    aFetchedResultsController.delegate = self
    _fetchedResultsController = aFetchedResultsController

    var error: NSError? = nil
    if !_fetchedResultsController!.performFetch(&error)
    {
        println("fetch error: \(error!.localizedDescription)")
        abort()
    }

    return _fetchedResultsController!
}    
var _fetchedResultsController: NSFetchedResultsController? = nil

override func viewDidLoad()
{
    super.viewDidLoad()


    // setup table view delegate and datasource
    self.tableView.dataSource = self
    self.tableView.delegate = self

    // pull-to-refresh setup
    self.refreshController.addTarget(self, action: "refreshTable:", forControlEvents: UIControlEvents.ValueChanged)
    self.tableView.addSubview(self.refreshController)
}

override func didReceiveMemoryWarning()
{
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

// MARK: - Table view data source

func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
    // Return the number of sections.
    return 1
}

// ask the NSFetchedResultsController for the section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
    let info = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
    return info.numberOfObjects
}

// create and configure each 'UITableViewCell'
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    let cell = tableView.dequeueReusableCellWithIdentifier(self.cellIdentifier, forIndexPath: indexPath) as SearchResultCell
    self.configureCell(cell, atIndexPath: indexPath)
    return cell
}

// helper method to configure a UITableViewCell ask NSFetchedResultsController for the model
func configureCell(cell: SearchResultCell, atIndexPath indexPath: NSIndexPath)
{

    let entity = self.fetchedResultsController.objectAtIndexPath(indexPath) as Entity
    cell.title.text = entity.name
}

// MARK: NSFetchedResultsController Delegate functions

func controllerWillChangeContent(controller: NSFetchedResultsController)
{
    self.tableView.beginUpdates()
}

func controllerDidChangeContent(controller: NSFetchedResultsController)
{
    self.tableView.endUpdates()
    self.tableView.reloadData()
    self.refreshController.endRefreshing()
}

/* Delegate method called:
        - when a new model is created
        - when an existing model is updated
        - when an existing model is deleted
*/
func controller(controller: NSFetchedResultsController,
    didChangeObject object: AnyObject,
    atIndexPath indexPath: NSIndexPath,
    forChangeType type: NSFetchedResultsChangeType,
    newIndexPath: NSIndexPath)
{
    switch type
    {
        case .Insert:
            self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        case .Update:
            let cell = self.tableView.cellForRowAtIndexPath(indexPath)
            self.configureCell(cell! as SearchResultCell, atIndexPath: indexPath)
            self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        case .Move:
            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        case .Delete:
            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        default:
            return
    }
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
{
    switch type
    {
        case .Insert:
            println("DEBUG: INSERT SECTION")
            self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
            break
        case .Delete:
            println("DEBUG: DELETE SECTION")
            self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
        default:
            return
    }
}

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
    self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

// MARK: Core Data Stack

/**
Save results response from server to CoreData entities
*/
private func saveSearchResultsResponse(response: AnyObject)
{
    self.deleteAllEntities("Entity")
    println("DEBUG: Saving new objects to model")
    // Search results object from response
    if let searchResultsDict = response as? [String: AnyObject]
    {
        if let entities = searchResultsDict["entities"] as? [AnyObject]
        {
            if let attributes = NSEntityDescription.entityForName("Entity", inManagedObjectContext: self.managedObjectContext!)?.attributesByName
            {
                // save entities
            }
        }

        }
    }

    // save changes persistent store
    var error : NSError?
    if !(self.managedObjectContext!.save(&error))
    {
        println("ERROR: Error saving model: \(error?.localizedDescription)")
    }
}

func deleteAllEntities(entityName: String)
{
    var error: NSError? = nil
    let allEntityFetchRequest = NSFetchRequest(entityName: entityName)
    if let savedObjects = self.managedObjectContext?.executeFetchRequest(allEntityFetchRequest, error: &error) as? [NSManagedObject]
    {
        for object in savedObjects
        {
            self.managedObjectContext?.deleteObject(object as NSManagedObject)
        }
        // save changes persistent store
        if !(self.managedObjectContext!.save(&error))
        {
            println("ERROR: Error saving model: \(error?.localizedDescription)")
        }
    }
    else
    {
        println("ERROR: Fetch error: \(error!.localizedDescription)")
    }
}
我尝试过的方法包括:

  • 在NSFetchedResultsController上设置并递增以下内容
    • 获取批大小
    • 取数限制
  • 经过长时间的使用和研究,它们看起来像是内存优化,对表视图显示的数据没有影响。显然这条路并不正确

  • 记录我希望显示的项目数量(例如5个)。然后,每次用户到达底部时,将其增加一个偏移量(例如,再增加5)。还返回numberOfRowsInSection数组的“count”。这最终导致EXC_BAD_访问崩溃和核心数据上下文不匹配 数据仍使用以下方式显示在CellForRowatineXpath中:

    func configureCell(cell: SearchResultCell, atIndexPath indexPath: NSIndexPath)
    {
        let entity = self.fFetchedResultsController.objectAtIndexPath(indexPath) as Entity
        ...
    }
    
    也许这是正确的方法,但我实施的方式是错误的

  • NSFetchedResultsController在更新后被调用,结果保存在数组中(例如,[实体])。查询此数组的计数以查找numberOfRowsInSection,并从indexPath.row处的数组项检索cellForRowAtIndexPath中的数据
  • 这会导致在填充单元格时EXC_BAD_ACCESS崩溃,但一旦重新加载应用程序,它会正确显示数据


    我真的很想坚持使用NSFetchedResultsController,因为我相信它有一些强大的排序/谓词功能,我想在我的应用程序中加以利用,但我目前在这方面陷入僵局。如果其他地方已经明确回答了,请提前道歉,但我无法找到这样的答案,非常感谢您的任何意见。

    方法2是我在阅读您的问题时想到的技巧。您应该只需要像您所说的那样保留一个“count”,并在为行“count-1”调用
    willDisplayCellForIndexPath:
    时增加它,并且count是