9

Ho un tableView che acquista il suo contenuto di cella da CoreData e ha sostituito lo SearchDisplayController (obsoleto) con il nuovo SearchController. Sto usando lo stesso controller tableView per presentare sia l'elenco completo degli oggetti sia gli oggetti filtrati/cercati.Eliminazione dai risultati della ricerca filtrata di UISearchController

Sono riuscito a far funzionare correttamente la ricerca/filtraggio e posso passare dall'elenco filtrato alle viste di dettaglio per tali elementi, quindi modificare e salvare le modifiche con successo alla tabella filtrata. Il mio problema è lo scorrimento per eliminare le celle dall'elenco filtrato causa un errore di runtime. In precedenza con il SearchDisplayController ho potuto farlo facilmente come ho avuto accesso al SearchDisplayController's risultati tableView e quindi la seguente (pseudo) codice dovrebbe funzionare bene:

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

Purtroppo tale tableView è esposto per la UISearchController e Im in un perdita. Ho provato a rendere il condizionale tableView.beginUpdates() e tableView.endUpdates() su tableView non essendo la tabella di ricerca, ma senza successo.

Per la cronaca questo è il mio messaggio di errore:

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

* EDIT *

mio tableView utilizza un FetchedResultsController per popolare se stesso da CoreData. Questa tabellaViewController è anche quella utilizzata da SearchController per visualizzare i risultati filtrati.

var searchController: UISearchController! 

Poi nel 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 

e

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() 
    } 
} 

Per quanto riguarda il messaggio di errore è interessato, io non sono sicuro di quanto posso aggiungere. L'app si blocca immediatamente dopo aver premuto il pulsante di cancellazione rosso (che rimane visualizzato) rivelato scorrendo. Questo è il log degli errori filo per 1 - 5. L'applicazione sembra bloccarsi sul numero 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) ->()() 

Spero che questo aiuta.

* 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

Si prega di condividere alcuni ulteriori dettagli sulla configurazione di SearchController, il metodo 'updateSearchResultsForSearchController' e ulteriori informazioni sul messaggio di errore? Grazie. – pbasdf

+0

Ho aggiunto qualche altra informazione alla mia risposta originale. Spero che questo ti aiuti. – Magnas

+0

Grazie. Sebbene l'errore si cristallizzi quando viene chiamato tableView.endUpdates(), penso che il problema risieda altrove. Sospetto che il problema è che dopo l'eliminazione, il numero restituito da 'numberOfRowsInSection' è incoerente con il valore precedente (se hai cancellato una riga, dovrebbe essere (il valore precedente - 1). Controlla (e/o aggiungi al tuo domanda) il tuo codice in 'commitEditingStyle' e' numberOfRowsInSection' e gli altri metodi delegati di FRC. – pbasdf

risposta

5

Il problema sorge a causa di una mancata corrispondenza tra l'indexPath usato dal controllore risultati recuperato e indexPath per la riga corrispondente nella tabellaView.

Mentre il controller di ricerca è attivo, il TableView esistente viene riutilizzato per visualizzare i risultati della ricerca. Da qui la tua logica per differenziare le due tabelle:

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

non necessario.Funziona, perché si imposta searchController.searchResultsUpdater = self quando si inizializza SearchController, quindi non è necessario cambiarlo, ma in entrambi i casi viene utilizzato lo stesso tableView.

La differenza sta nel modo in cui il TableView viene popolato mentre il SearchController è attivo. In tal caso, sembra (dal codice numberOfRowsInSection) come se i risultati filtrati venissero tutti visualizzati in una sezione. (Presumo che lo cellForRowAtIndexPath funzioni in modo simile.) Supponiamo di cancellare l'elemento nella sezione 0, riga 7, nei risultati filtrati. Poi commitEditingStyle saranno chiamati con indexPath 0-7, e la seguente riga:

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

cercherà di ottenere l'oggetto all'indice 0-7 della FRC. Ma l'elemento nell'indice 0-7 del FRC potrebbe essere un oggetto completamente diverso. Quindi cancelli l'oggetto sbagliato. Quindi i metodi del delegato FRC si attivano e indicano a TableView di eliminare la riga nell'indice 0-7. Ora, se l'oggetto realmente eliminato NON era nei risultati filtrati, il conteggio delle righe rimane invariato, anche se una riga è stata cancellata: da qui l'errore.

Così, per risolvere il problema, modificare la vostra commitEditingStyle in modo che trova l'oggetto giusto per eliminare, se il searchController è attivo:

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() 
     } 
    } 
} 

non sono stato in grado di testare quanto sopra; mi scuso se alcuni errori sono scivolati. Ma dovrebbe almeno puntare nella giusta direzione; spero che sia d'aiuto. Si noti che modifiche simili potrebbero essere richieste in alcuni altri metodi di visualizzazione/datasource di tableView.

+0

Molte grazie per aver dedicato il tempo e gli sforzi per spiegare questo in modo così chiaro. È molto apprezzato. – Magnas