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

bar*_*bus 6 core-data uitableview nsfetchedresultscontroller ios swift

现在已经挣扎了两个星期而没有找到足够的教程,SO通过我自己的许多失败的调查回答任何问题,我觉得最好在这里问一下,看看是否可以提供一个好的答案,或许可以节省我自己和很多其他人时间和头痛!

许多指南建议使用NSFetchedResultsController从Core Data存储中检索数据,特别是如果要在UITableView中显示数据.然而,几乎所有教程都假设您希望一次显示所有数据并停在那里.一旦我试图限制单元格数量并实现"加载更多"功能,它就会在接缝处开始崩溃.

在以下代码中(在Swift中实现),使用AFNetworking从API检索数据并将其保存到Core Data.保存数据发生在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)")
    }
}
Run Code Online (Sandbox Code Playgroud)

我试过的方法包括:

  1. 在NSFetchedResultsController上设置和递增以下内容
    • fetchBatchSize
    • fetchLimit

经过很长一段时间玩这些并研究它们,它们看起来像内存优化,并且对表视图显示的数据没有影响.绝对不是正确的道路.

  1. 保持我想要显示的项目数量(例如5).然后每当用户到达底部时将其递增一个偏移量(例如,另一个5).还为numberOfRowsInSection返回数组的'count' .这最终导致EXC_BAD_ACCESS崩溃和错误匹配的核心数据上下文.

使用以下内容仍然可以在cellForRowAtIndexPath中显示数据:

func configureCell(cell: SearchResultCell, atIndexPath indexPath: NSIndexPath)
{
    let entity = self.fFetchedResultsController.objectAtIndexPath(indexPath) as Entity
    ...
}
Run Code Online (Sandbox Code Playgroud)

也许这是正确的方法,但我实施错了?

  1. NSFetchedResultsController在更新后调用,结果保存在数组中(例如[Entity]).对numberOfRowsInSection查询此数组的计数,并从indexPath.row的数组项中检索cellForRowAtIndexPath中的数据.

这会导致填充单元格时EXC_BAD_ACCESS崩溃,但重新加载应用程序后,它会正确显示数据.

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