9

У меня есть tableView поиск его содержания клеток из CoreData и были замены SearchDisplayController (устаревшее) с новым SearchController. Я использую тот же контроллер tableView, чтобы представить как полный список объектов, так и отфильтрованные/проверенные объекты.Удаления из отфильтрованного поиска UISearchController в результате

Мне удалось добиться оптимальной работы поиска/фильтрации и перейти от фильтрованного списка к подробным представлениям для этих элементов, а затем отредактировать и сохранить изменения обратно в отфильтрованный tableView. Моя проблема заключается в том, что для удаления ячеек из отфильтрованного списка возникает ошибка времени выполнения. Ранее с SearchDisplayController я мог бы сделать это легко, поскольку я имел доступ к Tableview SearchDisplayController's результатов и поэтому следующее (псевдо) код будет работать нормально:

func controllerDidChangeContent(controller: NSFetchedResultsController) { 
    // If the search is active do this 
      searchDisplayController!.searchResultsTableView.endUpdates() 
    // else it isn't active so do this 
      tableView.endUpdates() 
    } 
} 

К сожалению, нет такого Tableview не подвергается воздействию на UISearchController и Im в точке а потеря. Я пробовал сделать tableView.beginUpdates() и tableView.endUpdates() условным на tableView, не являющимся таблицей поиска, но без успеха.

Для записи это мое сообщение об ошибке:

Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit_Sim/UIKit-3318.65/UITableView.m:1582

* EDIT *

Мой Tableview использует FetchedResultsController заселить себя от CoreData. Этот tableViewController также используется для поиска фильтрованных результатов, используемых SearchController.

var searchController: UISearchController! 

Тогда в ViewDidLoad

searchController = UISearchController(searchResultsController: nil) 
searchController.dimsBackgroundDuringPresentation = false 
searchController.searchResultsUpdater = self 
searchController.searchBar.sizeToFit() 
self.tableView.tableHeaderView = searchController?.searchBar 
self.tableView.delegate = self 
self.definesPresentationContext = true 

и

func updateSearchResultsForSearchController(searchController: UISearchController) { 
    let searchText = self.searchController?.searchBar.text 
    if let searchText = searchText { 
     searchPredicate = searchText.isEmpty ? nil : NSPredicate(format: "locationName contains[c] %@", searchText) 
     self.tableView.reloadData() 
    } 
} 

До сих пор, как сообщение об ошибке, то я не уверен, сколько я могу добавить. Приложение зависает сразу после нажатия красной кнопки удаления (которая остается показной), обнаруженной путем прокрутки. Это нить журнал ошибок 1 - 5. Приложение подвисает на номер 4.

#0 0x00000001042fab8a in objc_exception_throw() 
#1 0x000000010204b9da in +[NSException raise:format:arguments:]() 
#2 0x00000001027b14cf in -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:]() 
#3 0x000000010311169a in -[UITableView _endCellAnimationsWithContext:]() 
#4 0x00000001019b16f3 in iLocations.LocationViewController.controllerDidChangeContent (iLocations.LocationViewController)(ObjectiveC.NSFetchedResultsController) ->() at /Users/neilmckay/Dropbox/Programming/My Projects/iLocations/iLocations/LocationViewController.swift:303 
#5 0x00000001019b178a in @objc iLocations.LocationViewController.controllerDidChangeContent (iLocations.LocationViewController)(ObjectiveC.NSFetchedResultsController) ->()() 

Я надеюсь, что некоторые это помогает.

* 2 * EDIT

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 
    if editingStyle == .Delete { 
     let location: Location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location 
     location.removePhotoFile() 

     let context = self.fetchedResultsController.managedObjectContext 
     context.deleteObject(location) 

     var error: NSError? = nil 
     if !context.save(&error) { 
      abort() 
     } 
    } 
} 

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    if self.searchPredicate == nil { 
     let sectionInfo = self.fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo 
     return sectionInfo.numberOfObjects 
    } else { 
     let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() { 
      return self.searchPredicate!.evaluateWithObject($0) 
     } 
     return filteredObjects == nil ? 0 : filteredObjects!.count 
    } 
} 

// MARK: - NSFetchedResultsController methods 

var fetchedResultsController: NSFetchedResultsController { 
    if _fetchedResultsController != nil { 
     return _fetchedResultsController! 
    } 

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

    // Set the batch size to a suitable number. 
    fetchRequest.fetchBatchSize = 20 

    // Edit the sort key as appropriate. 
    if sectionNameKeyPathString1 != nil { 
     let sortDescriptor1 = NSSortDescriptor(key: sectionNameKeyPathString1!, ascending: true) 
     let sortDescriptor2 = NSSortDescriptor(key: sectionNameKeyPathString2!, ascending: true) 
     fetchRequest.sortDescriptors = [sortDescriptor1, sortDescriptor2] 
    } else { 
     let sortDescriptor = NSSortDescriptor(key: "firstLetter", ascending: true) 
     fetchRequest.sortDescriptors = [sortDescriptor] 
    } 

    var sectionNameKeyPath: String 
    if sectionNameKeyPathString1 == nil { 
     sectionNameKeyPath = "firstLetter" 
    } else { 
     sectionNameKeyPath = sectionNameKeyPathString1! 
    } 

    // 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: sectionNameKeyPath, cacheName: nil /*"Locations"*/) 
    aFetchedResultsController.delegate = self 
    _fetchedResultsController = aFetchedResultsController 

    var error: NSError? = nil 
    if !_fetchedResultsController!.performFetch(&error) { 
     fatalCoreDataError(error) 
    } 

    return _fetchedResultsController! 
} 

var _fetchedResultsController: NSFetchedResultsController? = nil 

func controllerWillChangeContent(controller: NSFetchedResultsController) { 
    if searchPredicate == nil { 
     tableView.beginUpdates() 
    } else { 
     (searchController.searchResultsUpdater as LocationViewController).tableView.beginUpdates() 
    } 

// tableView.beginUpdates()}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { 
    var tableView = UITableView() 
    if searchPredicate == nil { 
     tableView = self.tableView 
    } else { 
     tableView = (searchController.searchResultsUpdater as LocationViewController).tableView 
    } 

    switch type { 
    case .Insert: 
     tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) 
    case .Delete: 
     tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade) 
    default: 
     return 
    } 
} 

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath) { 
    var tableView = UITableView() 
    if searchPredicate == nil { 
     tableView = self.tableView 
    } else { 
     tableView = (searchController.searchResultsUpdater as LocationViewController).tableView 
    } 

    switch type { 
    case .Insert: 
     println("*** NSFetchedResultsChangeInsert (object)") 
     tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade) 

    case .Delete: 
     println("*** NSFetchedResultsChangeDelete (object)") 
      tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 
    case .Update: 
     println("*** NSFetchedResultsChangeUpdate (object)") 
     if searchPredicate == nil { 
      let cell = tableView.cellForRowAtIndexPath(indexPath) as LocationCell 
      let location = controller.objectAtIndexPath(indexPath) as Location 
      cell.configureForLocation(location) 
     } else { 
      let cell = tableView.cellForRowAtIndexPath(searchIndexPath) as LocationCell 
      let location = controller.objectAtIndexPath(searchIndexPath) as Location 
      cell.configureForLocation(location) 
     } 
    case .Move: 
     println("*** NSFetchedResultsChangeMove (object)") 
     tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 
     tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade) 
    } 
} 

func controllerDidChangeContent(controller: NSFetchedResultsController) { 
    if searchPredicate == nil { 
     tableView.endUpdates() 
    } else { 
     (searchController.searchResultsUpdater as LocationViewController).tableView.endUpdates() 
    } 
} 
+0

Пожалуйста, можете ли вы предоставить более подробную информацию о своей конфигурации SearchController, методе «updateSearchResultsForSearchController» и дополнительной информации об этом сообщении об ошибке? Благодарю. – pbasdf

+0

Я добавил дополнительную информацию в свой первоначальный ответ. Надеюсь это поможет. – Magnas

+0

Спасибо. Хотя ошибка возникает при вызове tableView.endUpdates(), я думаю, что проблема кроется в другом месте. Я подозреваю, что проблема заключается в том, что после удаления число, возвращаемое 'numberOfRowsInSection', не соответствует предыдущему значению (если вы удалили одну строку, это должно быть (предыдущее значение - 1). Проверить (и/или добавить к вашему вопрос) ваш код в 'commitEditingStyle' и' numberOfRowsInSection' и другие методы делегирования FRC. – pbasdf

ответ

5

Проблема возникает из-за несоответствия между indexPath, используемого извлечённому контроллера результатов и indexPath для соответствующей строки в таблицеView.

Несмотря на то, что контроллер поиска активен, существующий tableView используется повторно для отображения результатов поиска. Следовательно, ваша логика для дифференциации двух табличных экранов:

if searchPredicate == nil { 
    tableView = self.tableView 
} else { 
    tableView = (searchController.searchResultsUpdater as LocationViewController).tableView 
} 

не нужно.Он работает, потому что вы устанавливаете searchController.searchResultsUpdater = self, когда вы инициализируете searchController, поэтому нет необходимости его изменять, но тот же tableView используется в любом случае.

Разница заключается в том, как tableView заполняется, пока активен searchController. В этом случае он выглядит (из кода numberOfRowsInSection), как будто отфильтрованные результаты отображаются в одном разделе. (Предполагаю, что cellForRowAtIndexPath работает аналогичным образом.) Предположим, вы удалили элемент в разделе 0, строка 7, в отфильтрованных результатах. Тогда commitEditingStyle будет называться с indexPath 0-7 и в следующей строке:

let location: Location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location 

попытается получить объект с индексом 0-7 из КОП. Но элемент с индексом 0-7 FRC может быть совершенно другим объектом. Следовательно, вы удаляете неправильный объект. Затем методы делегата FRC запускаются и сообщают tableView удалить строку с индексом 0-7. Теперь, если действительно удаленный объект был НЕ в отфильтрованных результатах, количество строк будет неизменным, даже если строка была удалена: следовательно, ошибка.

Таким образом, чтобы это исправить, изменить ваш commitEditingStyle так, что он находит правильный объект для удаления, если searchController активен:

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 
    if editingStyle == .Delete { 
     var location : Location 
     if searchPredicate == nil { 
      location = self.fetchedResultsController.objectAtIndexPath(indexPath) as Location 
     } else { 
      let filteredObjects = self.fetchedResultsController.fetchedObjects?.filter() { 
       return self.searchPredicate!.evaluateWithObject($0) 
      } 
      location = filteredObjects![indexPath.row] as Location 
     } 
     location.removePhotoFile() 

     let context = self.fetchedResultsController.managedObjectContext 
     context.deleteObject(location) 

     var error: NSError? = nil 
     if !context.save(&error) { 
      abort() 
     } 
    } 
} 

Я не был в состоянии проверить выше; извините, если некоторые ошибки проскользнули. Но он должен, по крайней мере, указывать в правильном направлении; Надеюсь, поможет. Обратите внимание, что подобные изменения могут потребоваться в некоторых других методах делегирования/источника данных таблицыView.

+0

Большое спасибо за то, что нашли время и силы, чтобы объяснить это так ясно. – Magnas