UISearchDisplayController animate reloadData

狂风中的少年 提交于 2019-12-18 09:12:21

问题


I've been reading all the documentation about UISearchDisplayController and its delegate but I can't find any way to animate the table view when the search criteria change.

I'm using these two methods :

They both return YES but still can't find any way to do something similar to :

I don't know if it's important but I'm using an NSfetchedResultsController to populate the UITableView in the UISearchDisplayController

That's it thanks !


回答1:


When the search string or scope has changed, you assign a new fetch request for the fetched results controller and therefore have to call performFetch to get a new result set. performFetch resets the state of the controller, and it does not trigger any of the FRC delegate methods.

So the table view has to be updated "manually" after changing the fetch request. In most sample programs, this is done by

  • calling reloadData on the search table view, or
  • returning YES from shouldReloadTableForSearchString or shouldReloadTableForSearchScope.

The effect is the same: The search table view is reloaded without animation.

I don't think there is any built-in/easy method to animate the table view update when the search predicate changes. However, you could try the following (this is just an idea, I did not actually try this myself):

  • Before changing the fetch request, make a copy of the old result set:

    NSArray *oldList = [[fetchedResultsController fetchedObjects] copy];
    
  • Assign the new fetch request to the FRC and call performFetch.

  • Get the new result set:

    NSArray *newList = [fetchedResultsController fetchedObjects];
    
  • Do not call reloadData on the search table view.

  • Compare oldList and newList to detect new and removed objects. Call insertRowsAtIndexPaths for the new objects and deleteRowsAtIndexPaths for the removed objects. This can be done by traversing both lists in parallel.
  • Return NO from shouldReloadTableForSearchString or shouldReloadTableForSearchScope.

As I said, this is just an idea but I think it could work.




回答2:


I know this question is old, but I recently faced this and wanted a solution that would work regardless of how the NSFetchedResultsController was initialized. It's based on @martin-r's answer above.

Here's the corresponding gist: https://gist.github.com/stephanecopin/4ad7ed723f9857d96a777d0e7b45d676

import CoreData

extension NSFetchedResultsController {
    var predicate: NSPredicate? {
        get {
            return self.fetchRequest.predicate
        }
        set {
            try! self.setPredicate(newValue)
        }
    }

    var sortDescriptors: [NSSortDescriptor]? {
        get {
            return self.fetchRequest.sortDescriptors
        }
        set {
            try! self.setSortDescriptors(newValue)
        }
    }

    func setPredicate(predicate: NSPredicate?) throws {
        try self.setPredicate(predicate, sortDescriptors: self.sortDescriptors)
    }

    func setSortDescriptors(sortDescriptors: [NSSortDescriptor]?) throws {
        try self.setPredicate(self.predicate, sortDescriptors: sortDescriptors)
    }

    func setPredicate(predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]?) throws {
        func updateProperties() throws {
            if let cacheName = cacheName {
                NSFetchedResultsController.deleteCacheWithName(cacheName)
            }

            self.fetchRequest.predicate = predicate
            self.fetchRequest.sortDescriptors = sortDescriptors
            try self.performFetch()
        }

        guard let delegate = self.delegate else {
            try updateProperties()
            return
        }

        let previousSections = self.sections ?? []
        let previousSectionsCount = previousSections.count
        var previousObjects = Set(self.fetchedObjects as? [NSManagedObject] ?? [])
        var previousIndexPaths: [NSManagedObject: NSIndexPath] = [:]
        previousObjects.forEach {
            previousIndexPaths[$0] = self.indexPathForObject($0)
        }
        try updateProperties()
        let newSections = self.sections ?? []
        let newSectionsCount = newSections.count
        var newObjects = Set(self.fetchedObjects as? [NSManagedObject] ?? [])
        var newIndexPaths: [NSManagedObject: NSIndexPath] = [:]
        newObjects.forEach {
            newIndexPaths[$0] = self.indexPathForObject($0)
        }

        let updatedObjects = newObjects.intersect(previousObjects)
        previousObjects.subtractInPlace(updatedObjects)
        newObjects.subtractInPlace(updatedObjects)

        var moves: [(object: NSManagedObject, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)] = []
        updatedObjects.forEach { updatedObject in
            if let previousIndexPath = previousIndexPaths[updatedObject],
                let newIndexPath = newIndexPaths[updatedObject]
            {
                if previousIndexPath != newIndexPath {
                    moves.append((updatedObject, previousIndexPath, newIndexPath))
                }
            }
        }

        if moves.isEmpty && previousObjects.isEmpty && newObjects.isEmpty {
            // Nothing really changed
            return
        }

        delegate.controllerWillChangeContent?(self)

        moves.forEach {
            delegate.controller?(self, didChangeObject: $0.object, atIndexPath: $0.fromIndexPath, forChangeType: .Move, newIndexPath: $0.toIndexPath)
        }

        let sectionDifference = newSectionsCount - previousSectionsCount
        if sectionDifference < 0 {
            (newSectionsCount..<previousSectionsCount).forEach {
                delegate.controller?(self, didChangeSection: previousSections[$0], atIndex: $0, forChangeType: .Delete)
            }
        } else if sectionDifference > 0 {
            (previousSectionsCount..<newSectionsCount).forEach {
                delegate.controller?(self, didChangeSection: newSections[$0], atIndex: $0, forChangeType: .Insert)
            }
        }

        previousObjects.forEach {
            delegate.controller?(self, didChangeObject: $0, atIndexPath: previousIndexPaths[$0], forChangeType: .Delete, newIndexPath: nil)
        }
        newObjects.forEach {
            delegate.controller?(self, didChangeObject: $0, atIndexPath: nil, forChangeType: .Insert, newIndexPath: newIndexPaths[$0])
        }

        delegate.controllerDidChangeContent?(self)
    }
}

It exposes 2 properties on the NSFetchedResultsController, predicate and sortDescriptors which mirrors those found in the fetchRequest.
When either of these properties are set, the controller will automatically compute changes the changes and send them through the delegate, so hopefully there is no major code changes. If you don't want animations, you can still directly set predicate or sortDescriptors on the fetchRequest itself.



来源:https://stackoverflow.com/questions/14797224/uisearchdisplaycontroller-animate-reloaddata

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!