it-swarm.com.de

Filtern von NSFetchedResultsController (CoreData) mit UISearchDisplayController/UISearchBar

Ich versuche, Suchcode in meiner CoreData-basierten iPhone-App zu implementieren. Ich bin nicht sicher, wie ich vorgehen soll. Die App verfügt bereits über einen NSFetchedResultsController mit einem Prädikat zum Abrufen der Daten für die primäre TableView. Ich möchte sicherstellen, dass ich auf dem richtigen Weg bin, bevor ich zu viel Code ändere. Ich bin verwirrt, weil so viele Beispiele Array-basiert anstelle von CoreData sind.

Hier sind einige Fragen:

  1. Benötige ich einen zweiten NSFetchedResultsController, der nur die übereinstimmenden Elemente abruft, oder kann ich dieselbe als primäre TableView verwenden?

  2. Wenn ich dasselbe benutze, ist es so einfach, den FRC-Cache zu löschen und dann das Prädikat in der handleSearchForTerm: searchString-Methode zu ändern. Muss das Prädikat das ursprüngliche Prädikat sowie die Suchbegriffe enthalten, oder erinnert es sich daran, dass es ein Prädikat verwendet hat, um Daten überhaupt abzurufen?

  3. Wie komme ich zu den ursprünglichen Ergebnissen zurück? Muss ich das Suchprädikat einfach auf null setzen? Wird das ursprüngliche Prädikat, das zum Abrufen der FRC-Ergebnisse verwendet wurde, überhaupt nicht beendet?

Wenn jemand Beispiele für die Suche mit dem FRC-Code hat, würde ich mich sehr freuen!

144
jschmidt

Ich habe dies gerade bei einem meiner Projekte implementiert (Ihre Frage und die andere falsche Antwort deuteten an, was zu tun ist). Ich habe Sergios Antwort ausprobiert, hatte jedoch Ausnahmebedingungen, wenn ich tatsächlich auf einem Gerät laufe.

Ja, Sie erstellen zwei Abrufergebnis-Controller: einen für die normale Anzeige und einen für die Tabellensicht von UISearchBar.

Wenn Sie nur einen FRC (NSFetchedResultsController) verwenden, werden möglicherweise während der Suche in der ursprünglichen UITableView (nicht der während der Suche aktiven Suchtabellenansicht) Rückrufe aufgerufen. Sie versuchen, die gefilterte Version Ihres FRC falsch zu verwenden, und es werden Ausnahmen angezeigt über falsche Anzahl von Abschnitten oder Zeilen in Abschnitten geworfen.

Folgendes habe ich getan: Ich habe zwei FRCs als Eigenschaften fetchedResultsController und searchFetchedResultsController. Der searchFetchedResultsController sollte nicht verwendet werden, es sei denn, es gibt eine Suche (wenn die Suche abgebrochen wird, können Sie unten sehen, dass dieses Objekt freigegeben ist). Alle UITableView-Methoden müssen herausfinden, aus welcher Tabellensicht sie abfragen und aus welchem ​​FRC die Informationen abgerufen werden. Die FRC-Delegatmethoden müssen auch herausfinden, welche tableView aktualisiert werden soll.

Es ist überraschend, wie viel davon Boilerplate-Code ist.

Relevante Bits der Headerdatei:

@interface BlahViewController : UITableViewController <UISearchBarDelegate, NSFetchedResultsControllerDelegate, UISearchDisplayDelegate> 
{
    // other class ivars

    // required ivars for this example
    NSFetchedResultsController *fetchedResultsController_;
    NSFetchedResultsController *searchFetchedResultsController_;
    NSManagedObjectContext *managedObjectContext_;

    // The saved state of the search UI if a memory warning removed the view.
    NSString        *savedSearchTerm_;
    NSInteger       savedScopeButtonIndex_;
    BOOL            searchWasActive_;
}
@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain, readonly) NSFetchedResultsController *fetchedResultsController;

@property (nonatomic, copy) NSString *savedSearchTerm;
@property (nonatomic) NSInteger savedScopeButtonIndex;
@property (nonatomic) BOOL searchWasActive;

relevante Bits der Implementierungsdatei:

@interface BlahViewController ()
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSFetchedResultsController *searchFetchedResultsController;
@property (nonatomic, retain) UISearchDisplayController *mySearchDisplayController;
@end

Ich habe eine hilfreiche Methode zum Abrufen des korrekten FRC erstellt, wenn mit allen UITableViewDelegate/DataSource-Methoden gearbeitet wird:

- (NSFetchedResultsController *)fetchedResultsControllerForTableView:(UITableView *)tableView
{
    return tableView == self.tableView ? self.fetchedResultsController : self.searchFetchedResultsController;
}

- (void)fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController configureCell:(UITableViewCell *)theCell atIndexPath:(NSIndexPath *)theIndexPath
{
    // your cell guts here
}

- (UITableViewCell *)tableView:(UITableView *)theTableView cellForRowAtIndexPath:(NSIndexPath *)theIndexPath
{
    CallTableCell *cell = (CallTableCell *)[theTableView dequeueReusableCellWithIdentifier:@"CallTableCell"];
    if (cell == nil) 
    {
        cell = [[[CallTableCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CallTableCell"] autorelease];
    }

    [self fetchedResultsController:[self fetchedResultsControllerForTableView:theTableView] configureCell:cell atIndexPath:theIndexPath];
    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
    NSInteger count = [[[self fetchedResultsControllerForTableView:tableView] sections] count];

    return count;
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{
    NSInteger numberOfRows = 0;
    NSFetchedResultsController *fetchController = [self fetchedResultsControllerForTableView:tableView];
    NSArray *sections = fetchController.sections;
    if(sections.count > 0) 
    {
        id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
        numberOfRows = [sectionInfo numberOfObjects];
    }

    return numberOfRows;

}

Delegierungsmethoden für die Suchleiste:

#pragma mark -
#pragma mark Content Filtering
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSInteger)scope
{
    // update the filter, in this case just blow away the FRC and let lazy evaluation create another with the relevant search info
    self.searchFetchedResultsController.delegate = nil;
    self.searchFetchedResultsController = nil;
    // if you care about the scope save off the index to be used by the serchFetchedResultsController
    //self.savedScopeButtonIndex = scope;
}


#pragma mark -
#pragma mark Search Bar 
- (void)searchDisplayController:(UISearchDisplayController *)controller willUnloadSearchResultsTableView:(UITableView *)tableView;
{
    // search is done so get rid of the search FRC and reclaim memory
    self.searchFetchedResultsController.delegate = nil;
    self.searchFetchedResultsController = nil;
}

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    [self filterContentForSearchText:searchString 
                               scope:[self.searchDisplayController.searchBar selectedScopeButtonIndex]];

    // Return YES to cause the search result table view to be reloaded.
    return YES;
}


- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
    [self filterContentForSearchText:[self.searchDisplayController.searchBar text] 
                               scope:[self.searchDisplayController.searchBar selectedScopeButtonIndex]];

    // Return YES to cause the search result table view to be reloaded.
    return YES;
}

stellen Sie sicher, dass Sie die richtige Tabellensicht verwenden, wenn Sie Updates von den FRC-Delegatmethoden erhalten:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{
    UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
    [tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller 
  didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex 
     forChangeType:(NSFetchedResultsChangeType)type 
{
    UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;

    switch(type) 
    {
        case NSFetchedResultsChangeInsert:
            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (void)controller:(NSFetchedResultsController *)controller 
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)theIndexPath 
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath 
{
    UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;

    switch(type) 
    {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self fetchedResultsController:controller configureCell:[tableView cellForRowAtIndexPath:theIndexPath] atIndexPath:theIndexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{
    UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchDisplayController.searchResultsTableView;
    [tableView endUpdates];
}

Andere Ansichtsinformationen:

- (void)loadView 
{   
    [super loadView];
    UISearchBar *searchBar = [[[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, 44.0)] autorelease];
    searchBar.autoresizingMask = (UIViewAutoresizingFlexibleWidth);
    searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
    self.tableView.tableHeaderView = searchBar;

    self.mySearchDisplayController = [[[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self] autorelease];
    self.mySearchDisplayController.delegate = self;
    self.mySearchDisplayController.searchResultsDataSource = self;
    self.mySearchDisplayController.searchResultsDelegate = self;
}

- (void)didReceiveMemoryWarning
{
    self.searchWasActive = [self.searchDisplayController isActive];
    self.savedSearchTerm = [self.searchDisplayController.searchBar text];
    self.savedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex];

    fetchedResultsController_.delegate = nil;
    [fetchedResultsController_ release];
    fetchedResultsController_ = nil;
    searchFetchedResultsController_.delegate = nil;
    [searchFetchedResultsController_ release];
    searchFetchedResultsController_ = nil;

    [super didReceiveMemoryWarning];
}

- (void)viewDidDisappear:(BOOL)animated
{
    // save the state of the search UI so that it can be restored if the view is re-created
    self.searchWasActive = [self.searchDisplayController isActive];
    self.savedSearchTerm = [self.searchDisplayController.searchBar text];
    self.savedScopeButtonIndex = [self.searchDisplayController.searchBar selectedScopeButtonIndex];
}

- (void)viewDidLoad
{
    // restore search settings if they were saved in didReceiveMemoryWarning.
    if (self.savedSearchTerm)
    {
        [self.searchDisplayController setActive:self.searchWasActive];
        [self.searchDisplayController.searchBar setSelectedScopeButtonIndex:self.savedScopeButtonIndex];
        [self.searchDisplayController.searchBar setText:savedSearchTerm];

        self.savedSearchTerm = nil;
    }
}

FRC-Erstellungscode:

- (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString *)searchString
{
    NSArray *sortDescriptors = // your sort descriptors here
    NSPredicate *filterPredicate = // your predicate here

    /*
     Set up the fetched results controller.
     */
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *callEntity = [MTCall entityInManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:callEntity];

    NSMutableArray *predicateArray = [NSMutableArray array];
    if(searchString.length)
    {
        // your search predicate(s) are added to this array
        [predicateArray addObject:[NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", searchString]];
        // finally add the filter predicate for this view
        if(filterPredicate)
        {
            filterPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:filterPredicate, [NSCompoundPredicate orPredicateWithSubpredicates:predicateArray], nil]];
        }
        else
        {
            filterPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:predicateArray];
        }
    }
    [fetchRequest setPredicate:filterPredicate];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    [fetchRequest setSortDescriptors:sortDescriptors];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
                                                                                                managedObjectContext:self.managedObjectContext 
                                                                                                  sectionNameKeyPath:nil 
                                                                                                           cacheName:nil];
    aFetchedResultsController.delegate = self;

    [fetchRequest release];

    NSError *error = nil;
    if (![aFetchedResultsController performFetch:&error]) 
    {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return aFetchedResultsController;
}    

- (NSFetchedResultsController *)fetchedResultsController 
{
    if (fetchedResultsController_ != nil) 
    {
        return fetchedResultsController_;
    }
    fetchedResultsController_ = [self newFetchedResultsControllerWithSearch:nil];
    return [[fetchedResultsController_ retain] autorelease];
}   

- (NSFetchedResultsController *)searchFetchedResultsController 
{
    if (searchFetchedResultsController_ != nil) 
    {
        return searchFetchedResultsController_;
    }
    searchFetchedResultsController_ = [self newFetchedResultsControllerWithSearch:self.searchDisplayController.searchBar.text];
    return [[searchFetchedResultsController_ retain] autorelease];
}   
192
Brent Priddy

Einige haben kommentiert, dass dies mit einem einzigen NSFetchedResultsController möglich ist. Das habe ich getan und hier sind die Details. Bei dieser Lösung wird davon ausgegangen, dass Sie nur die Tabelle filtern und alle anderen Aspekte (Sortierreihenfolge, Zellenlayout usw.) der Suchergebnisse beibehalten möchten.

Definieren Sie zunächst zwei Eigenschaften in Ihrer UITableViewController-Unterklasse (ggf. mit dem entsprechenden @synthesize und dealloc):

@property (nonatomic, retain) UISearchDisplayController *searchController;
@property (nonatomic, retain) NSString *searchString;

Initialisieren Sie die Suchleiste in der viewDidLoad:-Methode Ihrer UITableViewController-Unterklasse:

UISearchBar *searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0,0,self.tableView.frame.size.width,44)]; 
searchBar.placeholder = @"Search";
searchBar.delegate = self;
self.searchController = [[[UISearchDisplayController alloc] initWithSearchBar:searchBar contentsController:self] autorelease];
self.searchController.delegate = self;
self.searchController.searchResultsDataSource = self;   
self.searchController.searchResultsDelegate = self; 
self.tableView.tableHeaderView = self.searchController.searchBar;
[searchBar release];

Drittens implementieren Sie die UISearchDisplayController-Delegatmethoden wie folgt:

// This gets called when you start typing text into the search bar
-(BOOL)searchDisplayController:(UISearchDisplayController *)_controller shouldReloadTableForSearchString:(NSString *)_searchString {
   self.searchString = _searchString;
   self.fetchedResultsController = nil;
   return YES;
}

// This gets called when you cancel or close the search bar
-(void)searchDisplayController:(UISearchDisplayController *)controller willUnloadSearchResultsTableView:(UITableView *)tableView {
   self.searchString = nil;
   self.fetchedResultsController = nil;
   [self.tableView reloadData];
}

Ändern Sie schließlich in der fetchedResultsController-Methode die NSPredicate-Funktion, je nachdem, ob self.searchString definiert ist:

-(NSFetchedResultsController *)fetchedResultsController {
   if (fetchedResultsController == nil) {

       // removed for brevity

      NSPredicate *predicate;

      if (self.searchString) {
         // predicate that uses searchString (used by UISearchDisplayController)
         // e.g., [NSPredicate predicateWithFormat:@"name CONTAINS[cd] %@", self.searchString];
          predicate = ... 
      } else {
         predicate = ... // predicate without searchString (used by UITableViewController)
      }

      // removed for brevity

   }

   return fetchedResultsController;
} 
18
chris

Ich brauchte ein paar Versuche, um das zum Laufen zu bringen ...

Mein Schlüssel zum Verständnis war, dass hier zwei TableViews am Werk sind. Einer, der von meinem Viewcontroller verwaltet wird, und einer, der vom Searchviewcontroller verwaltet wird. Dann konnte ich testen, welcher aktiv ist, und das Richtige tun. Die Dokumentation war auch hilfreich:

http://developer.Apple.com/library/ios/#documentation/uikit/reference/UISearchDisplayController_Class/Reference/Reference.html

Folgendes habe ich getan - 

Das Flag searchIsActive wurde hinzugefügt:

@interface ItemTableViewController : UITableViewController <NSFetchedResultsControllerDelegate, UISearchDisplayDelegate, UISearchBarDelegate> {

    NSString *sectionNameKeyPath;
    NSArray *sortDescriptors;


@private
    NSFetchedResultsController *fetchedResultsController_;
    NSManagedObjectContext *managedObjectContext_;

    BOOL searchIsActive;

}

@property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;
@property (nonatomic, retain) NSString *sectionNameKeyPath;
@property (nonatomic, retain) NSArray *sortDescriptors;
@property (nonatomic) BOOL searchIsActive;

Die Synthetisierung wurde in die Implementierungsdatei eingefügt.

Dann habe ich diese Methoden für die Suche hinzugefügt:

#pragma mark -
#pragma mark Content Filtering

- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
    NSFetchRequest *aRequest = [[self fetchedResultsController] fetchRequest];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", searchText];

    [aRequest setPredicate:predicate];

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        // Handle error
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }  

}

#pragma mark -
#pragma mark UISearchDisplayController Delegate Methods

- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
    [self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:nil];

    return YES;
}

/*
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption
{
    return YES;
}
*/

- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller {
    [self setSearchIsActive:YES];
    return;
}

- (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller 
{
    NSFetchRequest *aRequest = [[self fetchedResultsController] fetchRequest];

    [aRequest setPredicate:nil];

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        // Handle error
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }  

    [self setSearchIsActive:NO];
    return;
}

Dann in controllerWillChangeContent:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{
    if ([self searchIsActive]) {
        [[[self searchDisplayController] searchResultsTableView] beginUpdates];
    }
    else  {
        [self.tableView beginUpdates];
    }
}

Und controllerDidChangeContent:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{
    if ([self searchIsActive]) {
        [[[self searchDisplayController] searchResultsTableView] endUpdates];
    }
    else  {
        [self.tableView endUpdates];
    }
}

Und löschen Sie den Cache, wenn Sie das Prädikat zurücksetzen.

Hoffe das hilft.

17
Rob Cohen

Ich war mit der gleichen Aufgabe konfrontiert und habe DIE EINFACHSTE MÖGLICHKEIT MÖGLICH gefunden, um sie zu lösen. In Kürze: Sie müssen eine weitere Methode definieren, die -fetchedResultsController mit einem benutzerdefinierten zusammengesetzten Prädikat sehr ähnlich ist.

In meinem persönlichen Fall sieht mein -fetchedResultsController so aus:

- (NSFetchedResultsController *) fetchedResultsController
{
    if (fetchedResultsController != nil)
    {
        return fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Client"
                                              inManagedObjectContext:[[PTDataManager sharedManager] managedObjectContext]];
    [fetchRequest setEntity:entity];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"agency_server_id == %@", agency.server_id];
    fetchRequest.predicate = predicate;

    NSSortDescriptor *sortByName1Descriptor = [[NSSortDescriptor alloc] initWithKey:@"lastname" ascending:YES];
    NSSortDescriptor *sortByName2Descriptor = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
    NSSortDescriptor *sortByName3Descriptor = [[NSSortDescriptor alloc] initWithKey:@"middlename" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortByName1Descriptor, sortByName2Descriptor, sortByName3Descriptor, nil];

    fetchRequest.sortDescriptors = sortDescriptors;

    fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[[PTDataManager sharedManager] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
    fetchedResultsController.delegate = self;
    return fetchedResultsController;
}

Wie Sie sehen, hole ich Kunden einer mit agency.server_id-Prädikat gefilterten Agentur ab. Als Ergebnis rufe ich meinen Inhalt in einer tableView ab (alles im Zusammenhang mit der Implementierung von tableView und fetchedResultsController-Code ist ziemlich normal). Um searchField zu implementieren, definiere ich eine UISearchBarDelegate-Delegat-Methode. Ich löse es mit der Suchmethode aus, sagen Sie -reloadTableView:

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    [self reloadTableView];
}

und natürlich Definition von -reloadTableView:

- (void)reloadTableView
{
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Client"
                                              inManagedObjectContext:[[PTDataManager sharedManager] managedObjectContext]];
    [fetchRequest setEntity:entity];

    NSSortDescriptor *sortByName1Descriptor = [[NSSortDescriptor alloc] initWithKey:@"lastname" ascending:YES];
    NSSortDescriptor *sortByName2Descriptor = [[NSSortDescriptor alloc] initWithKey:@"firstname" ascending:YES];
    NSSortDescriptor *sortByName3Descriptor = [[NSSortDescriptor alloc] initWithKey:@"middlename" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortByName1Descriptor, sortByName2Descriptor, sortByName3Descriptor, nil];
    fetchRequest.sortDescriptors = sortDescriptors;

    NSPredicate *idPredicate = [NSPredicate predicateWithFormat:@"agency_server_id CONTAINS[cd] %@", agency.server_id];
    NSString *searchString = self.searchBar.text;
    if (searchString.length > 0)
    {
        NSPredicate *firstNamePredicate = [NSPredicate predicateWithFormat:@"firstname CONTAINS[cd] %@", searchString];
        NSPredicate *lastNamePredicate = [NSPredicate predicateWithFormat:@"lastname CONTAINS[cd] %@", searchString];
        NSPredicate *middleNamePredicate = [NSPredicate predicateWithFormat:@"middlename CONTAINS[cd] %@", searchString];
        NSPredicate *orPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:[NSArray arrayWithObjects:firstNamePredicate, lastNamePredicate, middleNamePredicate, nil]];
        NSPredicate *andPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:idPredicate, nil]];
        NSPredicate *finalPred = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:orPredicate, andPredicate, nil]];
        [fetchRequest setPredicate:finalPred];
    }
    else
    {
        [fetchRequest setPredicate:idPredicate];
    }

    self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:fetchRequest managedObjectContext:[[PTDataManager sharedManager] managedObjectContext] sectionNameKeyPath:nil cacheName:nil];
    self.fetchedResultsController.delegate = self;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error])
    {
        NSLog(@"Unresolved error %@, %@", [error localizedDescription], [error localizedFailureReason]);
    }; 

    [self.clientsTableView reloadData];
}

Diese Menge von Code ist der ersten "Standard" -fetchedResultsControllerSEHR &AUML;HNLICH, ABERin der if-else-Anweisung ist hier: 

+andPredicateWithSubpredicates: - Mit dieser Methode können wir ein Prädikat festlegen, um die Ergebnisse unseres ersten Hauptabrufs in der tableView zu speichern.

+orPredicateWithSubpredicates - Mit dieser Methode filtern wir den vorhandenen Abruf mit der Suchanfrage von searchBar.

Am Ende setze ich ein Array von Prädikaten als zusammengesetztes Prädikat für diesen bestimmten Abruf. AND für erforderliche Prädikate, OR für optional.

Und das ist alles! Sie müssen nichts weiter implementieren. Viel Spaß beim Kodieren!

5
Alex

Verwenden Sie eine Live-Suche?

Wenn Sie NICHT sind, möchten Sie wahrscheinlich ein Array (oder einen NSFetchedResultsController) mit den zuvor verwendeten Suchvorgängen. Wenn der Benutzer auf "Suchen" klickt, weisen Sie Ihren FetchedResults an, das Prädikat zu ändern.

In jedem Fall müssen Sie Ihre FetchedResults jedes Mal neu erstellen. Ich empfehle, nur einen NSFetchedResultsController zu verwenden, da Sie Ihren Code häufig duplizieren müssen und keinen Speicher für etwas verschwenden müssen, das Sie nicht anzeigen.

Stellen Sie nur sicher, dass Sie über eine NSString-Variable "searchParameters" verfügen, die von Ihrer FetchedResults-Methode nach Bedarf für Sie neu erstellt wird. Verwenden Sie dabei die Suchparameter, sofern verfügbar, sollten Sie Folgendes tun:

a) Setzen Sie "searchParameters" auf etwas (oder null, wenn Sie alle Ergebnisse erhalten möchten).

b) Geben Sie das aktuelle NSFetchedResultsController-Objekt frei und setzen Sie es auf Null.

c) Tabellendaten neu laden.

Hier ist ein einfacher Code:

- (void)searchString:(NSString*)s {
    self.searchResults = s;
    [fetchedResultsController release];
    fetchedResultsController = nil;
    [self.tableView reloadData];
}

-(NSFetchedResultsController *)fetchedResultsController {
    if (fetchedResultsController != nil) {
        return fetchedResultsController;
    }

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

    [fetchRequest setFetchBatchSize:20];

    // searchResults is a NSString*
    if (searchResults != nil) {
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name LIKE %@",searchResults];
        [fetchRequest setPredicate:predicate];
    }

    fetchedResultsController = 
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
        managedObjectContext:self.context sectionNameKeyPath:nil 
        cacheName:nil];
    fetchedResultsController.delegate = self;

    [fetchRequest release];

    return fetchedResultsController;    
}
5
Sergio Moura

Swift 3.0, UISearchController, NSFetchedResultsController und Kerndaten

Dieser Code funktioniert auf Swift 3.0 mit Core Data! Sie benötigen eine einzige Delegatmethode und einige Codezeilen, um Objekte aus dem Modell zu filtern und zu durchsuchen. Wenn Sie alle FRC- und ihre delegate-Methoden sowie searchController implementiert haben, ist nichts erforderlich.

Die UISearchResultsUpdating-Protokollmethode

func updateSearchResults(for searchController: UISearchController) {

    let text = searchController.searchBar.text

    if (text?.isEmpty)! {
       // Do something 
    } else {
        self.fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "( someString contains[cd] %@ )", text!)
    }
    do {
        try self.fetchedResultsController.performFetch()
        self.tableView.reloadData()
    } catch {}
}

Das ist es! Hoffe es hilft dir! Vielen Dank

4
Mannopson

Swift 3.0

Verwenden Sie ein textField, UISearchDisplayController ist seit iOS 8 veraltet, Sie müssten einen UISearchController verwenden. Warum erstellen Sie nicht Ihren eigenen Suchmechanismus, anstatt sich mit dem Search Controller zu befassen? Sie können ihn mehr anpassen und haben mehr Kontrolle darüber. Sie brauchen sich keine Sorgen zu machen, dass SearchController sich ändert und/oder veraltet wird. 

Diese Methode funktioniert sehr gut und erfordert nicht viel Code. Es ist jedoch erforderlich, dass Sie Core Data verwenden und NSFetchedResultsController implementieren.

Erstellen Sie zuerst ein TextField und registrieren Sie es mit einer Methode:

searchTextField?.addTarget(self, action: #selector(textFieldDidChange), for: UIControlEvents.editingChanged)

Dann erstellen Sie Ihre textFieldDidChange-Methode, die im Selektor beschrieben wurde, als das Ziel hinzugefügt wurde:

func textFieldDidChange() {
    if let queryString = searchTextField.text {
        filterList(queryString)
        self.tableView.reloadData()
    }
}

Dann möchten Sie die Liste in der filterList()-Methode mit dem NSPredicate- oder NSCompound-Prädikat filtern, wenn es komplexer ist. In meiner filterList-Methode filtere ich basierend auf dem Namen der Entität und dem Namen des "subCategories" -Objekts der Entität (eine Eins-zu-viele-Beziehung).

func filterList(_ queryString: String) {
    if let currentProjectID = Constants.userDefaults.string(forKey: Constants.CurrentSelectedProjectID) {
        if let currentProject = ProjectDBFacade.getProjectWithID(currentProjectID) {
            if (queryString != ""){
                let categoryPredicate = NSPredicate(format: "name CONTAINS[c] %@ && project == %@", queryString, currentProject)
                let subCategoryPredicate = NSPredicate(format: "subCategories.name CONTAINS[c] %@ && project == %@", queryString, currentProject)
                let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [categoryPredicate, subCategoryPredicate])
                fetchedResultsController.fetchRequest.predicate = orPredicate
            }else{
                fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "project == %@", currentProject)
            }

            do {
                try fetchedResultsController.performFetch()
            } catch {
                print("Error:  Could not fetch fetchedResultsController")
            }
        }
    }
}
1
Josh O'Connor

Ich denke, Luka hat dafür einen besseren Ansatz. Siehe LargeDataSetSample und seinen Grund

Er verwendet nicht FetchedResultsController, verwendet jedoch einen Cache für die Suche. Daher erscheinen die Suchergebnisse viel schneller, wenn der Benutzer mehr in SearchBar eingibt

Ich habe seinen Ansatz in meiner App verwendet und es funktioniert OK. Denken Sie auch daran, wenn Sie mit Model-Objekten arbeiten möchten, machen Sie es so einfach wie möglich. Siehe meine Antwort zu setPropertiesToFetch

0
onmyway133

Hier ist eine Methode zum Abrufen von fetchedResults mit mehreren Datensätzen, die sowohl einfach als auch allgemein genug ist, um sie nahezu überall anzuwenden. Packen Sie einfach Ihre Hauptergebnisse in ein Array, wenn eine Bedingung vorliegt.

NSArray *results = [self.fetchedResultsController fetchedObjects];

Fragen Sie das Array ab, indem Sie es durchlaufen lassen oder was immer Sie möchten, um eine Teilmenge Ihrer wichtigsten abgerufenenErgebnisse zu erstellen. Und jetzt können Sie entweder den vollständigen Satz oder einen Teilsatz verwenden, wenn eine Bedingung vorliegt.

0
smileBot

@Josh O'Connors Ansatz, bei dem er keine UISearchController verwendet, hat mir sehr gefallen. Dieser Controller (Xcode 9) hat immer noch einen Layoutfehler, den viele versuchen zu umgehen. 

Ich habe wieder eine UISearchBar anstelle einer UITextField verwendet, und es funktioniert ziemlich gut. Meine Anforderung an den Such-/Filter ist die Erstellung einer NSPredicate. Dies wird an den FRC übergeben:

   class ViewController: UIViewController, UITableViewDelegate, 
 UITableViewDataSource, UISearchBarDelegate {

        @IBOutlet var searchBar: UISearchBar!

         func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

                shouldShowSearchResults = true

                if let queryString = searchBar.text {
                    filterList(queryString)

                    fetchData()
                }
            }



          func filterList(_ queryString: String) {
        if (queryString == "") {
            searchPredicate = nil
    }
        else {
            let brandPredicate = NSPredicate(format: "brand CONTAINS[c] %@", queryString)
            let modelPredicate = NSPredicate(format: "model CONTAINS[c] %@", queryString)
            let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [brandPredicate, modelPredicate])
            searchPredicate = orPredicate
    }

}

...

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: filmEntity)
        request.returnsDistinctResults = true
        request.propertiesToFetch = ["brand", "model"]

        request.sortDescriptors = [sortDescriptor]
        request.predicate = searchPredicate

Verbinden Sie schließlich die SearchBar mit ihrem Delegierten.

Ich hoffe das hilft anderen

0
David DelMonte

Einfacher Ansatz zum Filtern vorhandener UITableView mithilfe von CoreData, der bereits nach Ihren Wünschen sortiert ist.

Dies wörtlich 5 Minuten, um mich einzurichten und zu arbeiten.

Ich hatte eine UITableView mit CoreData, die mit Daten von iCloud gefüllt wurde und die Benutzerinteraktionen ziemlich kompliziert hat, und ich wollte das alles nicht für eine UISearchViewController replizieren. Ich konnte der vorhandenen FetchRequest einfach ein Prädikat hinzufügen, das bereits von der FetchResultsController verwendet wurde, und das die bereits sortierten Daten filtert.

-(void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
    NSPredicate *filterPredicate;

    if(searchText != nil && searchText.length > 0)
        filterPredicate = [NSPredicate predicateWithFormat:@"(someField CONTAINS[cd] %@) OR (someOtherField CONTAINS[cd] %@)", searchText, searchText];
    else
        filterPredicate = nil;

    _fetchedResultsController.fetchRequest.predicate = filterPredicate;

    NSError *error = nil;
    [_fetchedResultsController performFetch:&error];
    [self.tableView reloadData];
}
0
Cliff Ribaudo