Як фільтрувати NSFetchedResultsController (CoreData) за допомогою UISearchDisplayController / UISearchBar


146

Я намагаюся реалізувати код пошуку в додатку iPhone на базі CoreData. Я не впевнений, як діяти далі. У додатку вже є NSFetchedResultsController з присудком для отримання даних для основного TableView. Я хочу переконатися, що я на правильному шляху, перш ніж змінити занадто багато коду. Я розгублений, оскільки так багато прикладів базується на масиві замість CoreData.

Ось кілька питань:

  1. Чи потрібно мені мати другий NSFetchedResultsController, який отримує лише відповідні елементи або я можу використовувати той самий, що і основний TableView?

  2. Якщо я використовую той самий, чи це так просто, як очистити кеш FRC, а потім змінити предикат у методі handleSearchForTerm: searchString? Чи повинен присудок містити початковий присудок, а також пошукові терміни чи пам'ятає, що він використовував предикат для отримання даних в першу чергу?

  3. Як повернутися до початкових результатів? Чи просто встановити предикат пошуку нульовим? Хіба це не вб'є в першу чергу оригінальний предикат, який використовувався для отримання результатів FRC?

Якщо у кого-небудь є приклади коду, що використовує пошук із FRC, я дуже вдячний!


@Brent, ідеальне рішення, приготував для мене частування!
детальноD

Відповіді:


193

Я фактично щойно реалізував це на одному зі своїх проектів (ваше запитання та інша неправильна відповідь натякали на те, що робити). Я спробував відповісти Серджіо, але виникли проблеми з виключеннями, коли він фактично працює на пристрої.

Так, ви створюєте два контролери результатів отримання: один для звичайного відображення та інший для подання таблиці UISearchBar.

Якщо ви використовуєте лише один FRC (NSFetchedResultsController), то в оригінальному UITableView (а не в таблиці пошуку, яка активна під час пошуку), можливо, будуть викликані зворотні дзвінки під час пошуку та спробуєте неправильно використовувати відфільтровану версію вашого FRC, і ви побачите винятки викинуто неправильну кількість розділів або рядків у розділах.

Ось що я зробив: у мене є два FRC, доступні як властивості fetchedResultsController та searchFetchedResultsController. SearchFetchedResultsController не слід використовувати, якщо немає пошуку (коли пошук скасовано, ви бачите нижче, що цей об’єкт звільнений). Усі методи UITableView повинні з'ясувати, у якому поданні таблиці він буде запитувати і в якому застосовному FRC для отримання інформації. Методи делегування FRC також повинні з'ясувати, який tableView оновлювати.

Дивно, скільки цього має код котла.

Відповідні біти файлу заголовка:

@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;

відновлені біти файлу реалізації:

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

Я створив корисний метод для отримання правильного FRC під час роботи з усіма методами UITableViewDelegate / DataSource:

- (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;

}

Методи делегування для рядка пошуку:

#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;
}

переконайтеся, що ви використовуєте правильний вигляд таблиці, отримуючи оновлення методів делегування FRC:

- (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];
}

Інша інформація про перегляд:

- (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:

- (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];
}   

3
Це, здається, працює прекрасно! Спасибі, Брент! Мені особливо подобається метод fetchedResultsControllerForTableView:. Це робить це дуже просто!
jschmidt

2
Смішно хороший код. Як сказав jschmidt, користувацький метод "fetchedResultsControllerForTableView:" дійсно спрощує весь процес.
Даніель Амітай

Brent. Ти чоловік. Але ось вам новий виклик. Реалізація цього коду за допомогою фонової обробки. Я зробив кілька незначних багатопотокових решт інших частин свого додатка, але це важко (як мінімум для мене). Я думаю, це додало б приємнішого користувальницького досвіду. Виклик прийнятий?
jschmidt

3
@BrentPriddy Дякую! Я перероблений код , щоб модифікувати Fetch Request замість установки searchFetchedResultsControllerна nilкожен раз , коли зміни тексту пошуку.
ma11hew28

2
У вашому cellForRowAtIndexPath, чи не слід вам дістати клітинку, self.tableViewяк хтось вказував на це питання? Якщо цього не зробити, спеціальна комірка не відображається.
ам

18

Деякі прокоментували, що це можна зробити за допомогою синглу NSFetchedResultsController. Це я і зробив, і ось деталі. Це рішення передбачає, що ви просто хочете відфільтрувати таблицю та підтримувати всі інші аспекти (порядок сортування, компонування комірок тощо) результатів пошуку.

Спочатку визначте два властивості у своєму UITableViewControllerпідкласі (з відповідними @synthesize та dealloc, якщо це можливо):

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

По-друге, ініціалізуйте панель пошуку viewDidLoad:методом свого UITableViewControllerпідкласу:

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];

По-третє, реалізуйте такі UISearchDisplayControllerделегатні методи:

// 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];
}

Нарешті, у fetchedResultsControllerметоді змінюють NSPredicateзалежність, якщо self.searchStringвизначено:

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

1
Це рішення добре працювало для мене, і це набагато простіше. Дякую! Я б тільки запропонував налаштувати "if (self.searchString)" to "if (self.searchString.length). Це запобігає краху, якщо ви натиснете на подання таблиці після запуску пошуку та видалення рядка з панелі пошуку.
Guto Araujo

17

Мені знадобилося кілька спроб, щоб це працювало ...

Моїм ключем до розуміння було розуміння того, що тут працюють два табличних перегляду. Один керував моїм viewcontroller, а інший керував searchviewcontroller, і тоді я міг би перевірити, чи є активним, і робити правильно. Документація також була корисною:

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

Ось що я зробив -

Додано прапор пошукуIsActive:

@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;

Додано синтез у файл реалізації.

Потім я додав ці методи пошуку:

#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;
}

Тоді в controllerWillChangeContent:

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

І controllerDidChangeContent:

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

І видаліть кеш при скиданні предиката.

Сподіваюся, це допомагає.


але я не розумію, приклад вище дуже хороший, але неповний, але ваша рекомендація має спрацювати, але це не так ...
Володимир Стажилов

Ви можете просто перевірити подання активної таблиці замість використання BOOL:if ( [self.tableView isEqual:self.searchDisplayController.searchResultsTableView] ) { ... }
rwyland

@rwyland - Моє тестування показує, що self.tableview не встановлено для searchdisplaycontroller.searchresultstableview, коли пошук активний. Вони ніколи не були б рівними.
giff

5

Я зіткнувся з тим же завданням і виявив, що найпростіший спосіб його вирішення. Незабаром: вам потрібно визначити ще один метод, дуже подібний до -fetchedResultsControllerспеціального складеного предиката.

В моєму особистому випадку моє -fetchedResultsControllerвиглядає так:

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

Як ви бачите, я добираю клієнтів одного агентства, відфільтрованих за agency.server_idприсудком. Як результат, я також отримую вміст у tableView(все, що стосується реалізації tableViewта fetchedResultsControllerкоду, є досить стандартним). Для реалізації searchFieldя визначаю UISearchBarDelegateметод делегата. Я запускаю це методом пошуку, скажімо -reloadTableView:

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

і, звичайно , визначення -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];
}

Цей пучок коду дуже схожий на перше, "стандартне", -fetchedResultsController але НЕ, якщо інше, тут:

+andPredicateWithSubpredicates: - за допомогою цього методу ми можемо встановити присудок для збереження результатів нашого основного першого вибору в tableView

+orPredicateWithSubpredicates - за допомогою цього методу ми фільтруємо існуючий виборку за пошуковим запитом від searchBar

Наприкінці я встановлюю масив предикатів як складений предикат для цього конкретного вибору. І для необхідних предикатів, АБО для необов'язкових.

І це все! Вам більше нічого не потрібно реалізовувати. Щасливого кодування!


5

Ви використовуєте пошук у прямому ефірі?

Якщо ви НЕ, ви, мабуть, хочете масив (або NSFetchedResultsController) з попередніми пошуковими запитами, які ви використовували, коли користувач натискає "пошук", ви скажете вашим FetchedResults змінити його предикат.

У будь-якому випадку, вам потрібно буде щоразу відновлювати свої FetchedResults. Я рекомендую використовувати лише один NSFetchedResultsController, оскільки вам доведеться багато разів дублювати код і вам не потрібно витрачати пам'ять на те, що ви не показуєте.

Просто переконайтеся, що у вас є змінна NSString "searchParameters", і ваш метод FetchedResults перебудує її для вас за необхідності, використовуючи параметри пошуку, якщо вони доступні, ви просто повинні зробити:

a) встановіть параметр "searchParameters" на щось (або нуль, якщо ви хочете, щоб усі результати).

b) відпустіть і встановіть на нуль поточний об'єкт NSFetchedResultsController.

в) перезавантажте дані таблиці.

Ось простий код:

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

Дуже цікаво! Я спробую.
jschmidt

Це, здається, працює, але не вдається, коли ваша таблиця заповнена FRC, searchTableView - це інша таблиця від основного виду таблиці, який ви використовуєте. Методи делегування FRC повсюдно просуваються, коли на пристрої з низькою пам’яттю під час пошуку і головний tableView хоче перезавантажити клітинки.
Brent Priddy

Хтось має для цього посилання на шаблон проекту? Мені важко зрозуміти, що куди йде. Було б дуже приємно мати робочий шаблон як орієнтир.
RyeMAC3

@Brent, Ви повинні перевірити, чи це пошук таблиці таблиць, який потребує змін у методах делегування FRC - якщо ви зробите та оновите правильну таблицю в методах делегатів FRC та UITableView, тоді при використанні FRC і для головного перегляду таблиці, і для параметра FRC все повинно бути нормально пошук таблиці.
kervich

@kervich Я вважаю, що ви описуєте мою відповідь вище або ви хочете сказати, що ви можете це зробити лише з одним FRC?
Brent Priddy

5

Swift 3.0, UISearchController, NSFetchedResultsController та основні дані

Цей код буде працювати на Swift 3.0 з Core Data! Вам знадобиться один метод делегата та кілька рядків коду для фільтрації та пошуку об’єктів із моделі. Нічого не знадобиться, якщо ви також реалізували всі FRCта їх delegateметоди searchController.

Метод UISearchResultsUpdatingпротоколу

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 {}
}

Це воно! Сподіваюся, це допоможе вам! Дякую


1

SWIFT 3.0

Використовуйте textField, UISearchDisplayController застаріло, як на iOS 8, вам доведеться використовувати UISearchController. Замість того, щоб мати справу з контролером пошуку, чому ви не створюєте власний механізм пошуку? Ви можете налаштувати його більше і мати більше контролю над ним, і не потрібно турбуватися про зміну SearchController та / або застарілу.

Цей метод, який я використовую, працює дуже добре, і йому не потрібно багато коду. Однак для цього потрібно використовувати основні дані та реалізувати NSFetchedResultsController.

Спочатку створіть TextField та зареєструйте його методом:

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

Потім створіть свій метод textFieldDidChange, описаний у селекторі, коли додано ціль:

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

Потім ви хочете відфільтрувати список за filterList()методом, використовуючи предикат NSPredicate або NSCompound, якщо він складніший. У своєму методі filterList я фільтрую на основі назви сутності та назви об'єктів "підкатегорії" (співвідношення "один до багатьох").

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

0

Я думаю, у Лука є кращий підхід до цього. Див. Зразки LargeDataSetSample та його причини

Він не FetchedResultsControllerвикористовує кеш, але використовує кеш під час пошуку, отже, результати пошуку з’являються набагато швидше, коли користувач більше вводить у SearchBar

Я використовував його підхід у своєму додатку, і він працює добре. Також пам’ятайте, якщо ви хочете працювати з об'єктом Model, зробити його максимально простим, дивіться мою відповідь про setPropertiesToFetch


0

Ось спосіб обробки fetchedResults з декількома наборами даних, який є простим і загальним, щоб застосувати практично будь-де. Просто перенесіть основні результати до масиву, коли є якась умова.

NSArray *results = [self.fetchedResultsController fetchedObjects];

Запросіть масив, переглянувши його або все, що завгодно, щоб створити підмножину головних вилучених результатів. А тепер ви можете або використовувати повний набір, або підмножину, коли є якась умова.


0

Мені дуже сподобався підхід @Josh O'Connor, де він не використовує UISearchController . Цей контролер досі (Xcode 9) має помилку у макеті, яку багато хто намагається вирішити.

Я повернувся до використання UISearchBarзамість UITextField, і це працює досить непогано. Моя вимога до пошуку / фільтра - це виготовлення NSPredicate. Це передається до FRC:

   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

Нарешті, підключіть SearchBar до свого делегата.

Я сподіваюся, що це допомагає іншим


0

Простий підхід до фільтрування існуючих UITableView за допомогою CoreData, який вже відсортований за вашим бажанням.

Це буквально занадто мені 5 хвилин, щоб налаштувати і працювати.

У мене було наявне UITableViewвикористання, CoreDataзаповнене даними від iCloud, яке має досить складні взаємодії з користувачами, і мені не хотілося повторювати все це для UISearchViewController. Мені вдалося просто додати присудок до FetchRequestвже використовуваних FetchResultsControllerі який фільтрує вже відсортовані дані.

-(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];
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.