1

Ich habe eine NSFetchedResultsController Anzeige einiger Daten aus Core Data und ich versuche, eine Suchleiste damit arbeiten zu bekommen. Die Suche funktioniert, aber controller(didChangeObject:) wird mit Indexpfaden von der vollständigen Tabelle aufgerufen, nicht von der gefilterten, sodass das Aktualisieren entweder den falschen Datensatz oder Fehler ändert, da der Indexpfad außerhalb des Bereichs liegt. Obwohl ich weiß, dass dies die Ursache des Fehlers ist, weiß ich nicht, wie ich das beheben soll. Die wesentlichen Teile meines Codes sind wie folgt:Wie kann ich NSFetchedResultsController Update mit einem UISearchController arbeiten lassen?

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 
    let cell = tableView.dequeueReusableCellWithIdentifier("PlayerTableViewCell", forIndexPath: indexPath) as! PlayerTableViewCell 
    let coreDataIndexPath: NSIndexPath 
    if searchController.active && searchController.searchBar.text != "" { 
     coreDataIndexPath = fetchedResultsController.indexPathForObject(filteredPlayers[indexPath.row])! 
    } else { 
     coreDataIndexPath = indexPath 
    } 
    configureCell(cell, atIndexPath: coreDataIndexPath) 
    return cell 
} 

override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 
    if editingStyle == .Delete { 
     // Fetch Record 
     let record = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject 

     // Delete Record 
     managedObjectContext.deleteObject(record) 
    } 
} 


// MARK: Fetched Results Controller Delegate Methods 
func controllerWillChangeContent(controller: NSFetchedResultsController) { 
    tableView.beginUpdates() 
} 

func controllerDidChangeContent(controller: NSFetchedResultsController) { 
    tableView.endUpdates() 
} 

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { 
    switch (type) { 
    case .Insert: 
     if let indexPath = newIndexPath { 
      tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 
     } 
    case .Delete: 
     if let indexPath = indexPath { 
      tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 
     } 
    case .Update: 
     if let indexPath = indexPath { 
      let cell = tableView.cellForRowAtIndexPath(indexPath) as! PlayerTableViewCell 
      configureCell(cell, atIndexPath: indexPath) 
     } 
    case .Move: 
     if let indexPath = indexPath { 
      tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 
     } 

     if let newIndexPath = newIndexPath { 
      tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade) 
     } 
    } 
} 

// MARK: UISearchResultsUpdating 
func updateSearchResultsForSearchController(searchController: UISearchController) { 
    if let searchText = searchController.searchBar.text { 
     let predicate = NSPredicate(format: "name CONTAINS %@", searchText) 
     if let fetchedObjects = fetchedResultsController.fetchedObjects as? [Player] { 
      filteredPlayers = fetchedObjects.filter({return predicate.evaluateWithObject($0)}) 
     } 
    } 
    tableView.reloadData() 

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 
    if searchController.active && searchController.searchBar.text != "" { 
     return filteredPlayers.count 
    } else if let sections = fetchedResultsController.sections { 
     let sectionInfo = sections[section] 
     return sectionInfo.numberOfObjects 
    } 

    return 0 
} 

configureCell lädt nur die Daten aus dem Datensatz bei indexPath in die Zelle.

Ich hatte zuvor versucht, die Suche zu tun, indem ich die fetchedResultsController.fetchRequest.predicate änderte, aber die Tabelle würde nicht zu normal zurückkehren, wenn die Suchleiste abgebrochen wurde, trotz meiner besten Bemühungen. Dieser Code war wie folgt:

// MARK: UISearchResultsUpdating 
func updateSearchResultsForSearchController(searchController: UISearchController) { 
    let searchText = searchController.searchBar.text! 
    let predicate = NSPredicate(format: "%K CONTAINS[c] %@", argumentArray: ["name", searchText]) 
    fetchedResultsController.fetchRequest.predicate = predicate 
    do { 
     try self.fetchedResultsController.performFetch() 
    } catch { 
     let fetchError = error as NSError 
     print("\(fetchError), \(fetchError.userInfo)") 
    } 
    tableView.reloadData() 
} 

// MARK: UISearchBarDelegate 
func searchBarCancelButtonClicked(searchBar: UISearchBar) { 
    fetchedResultsController.fetchRequest.predicate = nil 
    do { 
     try self.fetchedResultsController.performFetch() 
    } catch { 
     let fetchError = error as NSError 
     print("\(fetchError), \(fetchError.userInfo)") 
    } 
    tableView.reloadData() 
} 
+0

Haben Sie jemals herausgefunden? Ich habe die gleichen Probleme. – NRitH

Antwort

1

Es gibt zwei Dinge, die Sie hier tun müssen:

  • setzen Sie die NSFetchedResultsController für jede neue Suche Ereignis in updateSearchResultsForSearchController, bevor die Tabellendaten neu zu laden;
  • geben Sie Ihre cellForRowAtIndexPath als Zelle am IndexPath im FRC zurück. Da der Status der searchBar nicht abgefragt werden muss, ist der NSFetchedResultsController bereits auf dem neuesten Stand.

Ich bin nicht vertraut mit Swift-Syntax als der noch, aber hier ist, wie es in ObjC aussieht:

-(void)updateSearchResultsForSearchController:(UISearchController *)searchController{ 
    self.fetchedResultsController = nil; 
    [self.tableView reloadData]; 
} 


-(NSFetchedResultsController *)fetchedResultsController{ 
if (_fetchedResultsController == nil) { 
    NSManagedObjectContext *context = self.managedObjectContext; // Creating FRC with main MOC 

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"MyEntity" inManagedObjectContext:context]; 
    [fetchRequest setEntity:entity]; 

    NSString *searchString = [self.searchController.searchBar.text]; 
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@ ", searchString]; 

    [fetchRequest setPredicate:predicate]; 

    // Specify how the fetched objects should be sorted 
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"someField" ascending:YES]; 
    [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDescriptor, nil]]; 

    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil]; 

    NSError *error = nil; 
    if (![_fetchedResultsController performFetch:&error]) 
     NSLog(@"Error initializing FRC for Users. Error %@", error.userInfo); 

    _fetchedResultsController.delegate = self; 
    } 
return _fetchedResultsController; 
} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 
     cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell" forIndexPath:indexPath]; 

     if (cell == nil) 
       cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCell"]; 
     MyEntity *myObject = [self.fetchedResultsController objectAtIndexPath:indexPath]; 

    /* Populate cell content here */ 
    return cell; 
} 
+0

Funktioniert wie ein Charme. Nur eine Notiz. Der self.searchController kann auf die Eigenschaft isActive getestet werden. Daher kann der Initialization-Teil holedResultsController nur eine Bedingung für self.searchController.isActive mit einem body-NSPredicate-PrädikatWithFormat enthalten. –

Verwandte Themen