Cara memfilter NSFetchedResultsController (CoreData) dengan UISearchDisplayController / UISearchBar

146

Saya mencoba menerapkan kode pencarian di aplikasi iPhone berbasis CoreData saya. Saya tidak yakin bagaimana melanjutkan. Aplikasi ini sudah memiliki NSFetchedResultsController dengan predikat untuk mengambil data untuk TableView primer. Saya ingin memastikan saya berada di jalan yang benar sebelum saya mengubah terlalu banyak kode. Saya bingung karena begitu banyak contoh berbasis array bukan CoreData.

Berikut ini beberapa pertanyaan:

  1. Apakah saya perlu memiliki NSFetchedResultsController kedua yang hanya mengambil item yang cocok atau dapatkah saya menggunakan yang sama dengan TableView primer?

  2. Jika saya menggunakan yang sama, apakah semudah membersihkan cache FRC dan kemudian mengubah predikat di handleSearchForTerm: metode searchString? Apakah predikat tersebut harus berisi predikat awal dan juga istilah pencarian atau apakah ia ingat bahwa predikat itu digunakan untuk mengambil data?

  3. Bagaimana saya kembali ke hasil semula? Apakah saya hanya menetapkan predikat pencarian ke nihil? Bukankah itu akan membunuh predikat asli yang digunakan untuk mengambil hasil FRC di tempat pertama?

Jika ada yang punya contoh kode menggunakan pencarian dengan FRC, saya akan sangat menghargainya!

jschmidt
sumber
@ Bos, solusi sempurna, bekerja untuk saya!
DetartrateD

Jawaban:

193

Saya sebenarnya baru saja mengimplementasikan ini pada salah satu proyek saya (pertanyaan Anda dan jawaban salah lainnya mengisyaratkan apa yang harus dilakukan). Saya mencoba jawaban Sergio tetapi memiliki masalah pengecualian ketika benar-benar berjalan pada perangkat.

Ya, Anda membuat dua pengendali hasil ambil: satu untuk tampilan normal dan satu lagi untuk tampilan tabel UISearchBar.

Jika Anda hanya menggunakan satu FRC (NSFetchedResultsController) maka UITableView asli (bukan tampilan tabel pencarian yang sedang aktif saat mencari) mungkin akan memiliki panggilan balik yang dipanggil saat Anda mencari dan mencoba menggunakan versi FRC yang difilter dan Anda akan melihat pengecualian dilemparkan tentang jumlah bagian yang salah atau baris di bagian.

Inilah yang saya lakukan: Saya memiliki dua FRC yang tersedia sebagai properti fetchedResultsController dan searchFetchedResultsController. SearchFetchedResultsController tidak boleh digunakan kecuali jika ada pencarian (ketika pencarian dibatalkan, Anda dapat melihat di bawah ini bahwa objek ini dilepaskan). Semua metode UITableView harus mencari tahu tampilan tabel apa yang akan dicari dan dari FRC yang berlaku untuk menarik informasi. Metode delegasi FRC juga harus mencari tahu tableView mana yang akan diperbarui.

Sungguh mengejutkan betapa banyak dari ini adalah kode boilerplate.

Bit file header yang relevan:

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

relevan bit dari file implementasi:

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

Saya membuat metode yang bermanfaat untuk mengambil FRC yang benar ketika bekerja dengan semua metode 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;

}

Delegasikan metode untuk bilah pencarian:

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

pastikan bahwa Anda menggunakan tampilan tabel yang benar ketika mendapatkan pembaruan dari metode delegasi 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];
}

Informasi tampilan lainnya:

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

Kode pembuatan 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];
}   
Brent Priddy
sumber
3
Itu tampaknya bekerja dengan indah! Terima kasih, Brent! Saya terutama menyukai metode fetchedResultsControllerForTableView :. Itu membuatnya sangat mudah!
jschmidt
2
Kode yang sangat bagus. Seperti yang dikatakan jschmidt, metode "fetchedResultsControllerForTableView:" benar-benar menyederhanakan seluruh proses.
Daniel Amitay
Brent Kamulah orangnya. Tapi, inilah tantangan baru untuk Anda. Implementasi kode ini menggunakan pemrosesan latar belakang. Saya telah melakukan beberapa multi-threading kecil pada bagian lain dari aplikasi saya, tetapi ini sulit (setidaknya untuk saya). Saya pikir itu akan menambah pengalaman pengguna yang lebih baik. Tantangan diterima?
jschmidt
3
@BrentPriddy Terima kasih! Saya refactored kode Anda untuk Memodifikasi Permintaan Ambil alih-alih mengatur searchFetchedResultsControlleruntuk nilsetiap kali teks pencarian berubah.
ma11hew28
2
Dalam diri Anda cellForRowAtIndexPath, bukankah seharusnya Anda mendapatkan sel self.tableViewseperti seseorang yang ditunjukkan dalam pertanyaan SO ini ? Jika Anda tidak melakukan ini, sel khusus tidak ditampilkan.
amb
18

Beberapa berkomentar bahwa ini dapat dilakukan dengan satu NSFetchedResultsController . Itulah yang saya lakukan, dan inilah detailnya. Solusi ini mengasumsikan Anda hanya ingin memfilter tabel dan mempertahankan semua aspek lainnya (urutan, tata letak sel, dll.) Dari hasil pencarian.

Pertama, tentukan dua properti di UITableViewControllersubclass Anda (dengan @synthesize dan dealloc yang sesuai, jika berlaku):

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

Kedua, inisialisasi bilah pencarian dalam viewDidLoad:metode UITableViewControllersubclass Anda :

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

Ketiga, terapkan UISearchDisplayControllermetode delegasi seperti ini:

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

Akhirnya, dalam fetchedResultsControllermetode ini ubah NSPredicateketergantungan jika self.searchStringdidefinisikan:

-(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;
} 
chris
sumber
1
Solusi ini bekerja dengan baik untuk saya dan itu jauh lebih sederhana. Terima kasih! Saya hanya menyarankan tweaking 'if (self.searchString)' to 'if (self.searchString.length). Ini mencegahnya agar tidak mogok jika Anda mengklik tampilan tabel setelah memulai pencarian dan menghapus string dari bilah pencarian.
Guto Araujo
17

Saya perlu beberapa upaya untuk membuatnya berfungsi ...

Kunci pemahaman saya adalah menyadari bahwa ada dua tableViews yang bekerja di sini. Satu dikelola oleh viewcontroller saya dan satu dikelola oleh searchviewcontroller dan kemudian saya bisa menguji untuk melihat mana yang aktif dan melakukan hal yang benar. Dokumentasi juga membantu:

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

Inilah yang saya lakukan -

Menambahkan bendera searchIsActive:

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

Menambahkan synthesize dalam file implementasi.

Kemudian saya menambahkan metode ini untuk pencarian:

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

Kemudian di controllerWillChangeContent:

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

Dan controllerDidChangeContent:

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

Dan hapus cache saat mengatur ulang predikat.

Semoga ini membantu.

Rob Cohen
sumber
namun saya tidak mengerti, contoh di atas sangat bagus, tetapi tidak lengkap, tetapi rekomendasi Anda harus bekerja, tetapi tidak ...
Vladimir Stazhilov
Anda bisa saja memeriksa tampilan tabel aktif daripada menggunakan BOOL:if ( [self.tableView isEqual:self.searchDisplayController.searchResultsTableView] ) { ... }
rwyland
@rwyland - Pengujian saya menunjukkan bahwa self.tableview tidak diatur ke searchdisplaycontroller.searchresultstableview ketika pencarian aktif. Ini tidak akan pernah sama.
giff
5

Saya dihadapkan dengan tugas yang sama dan menemukan CARA YANG SEDERHANA untuk menyelesaikannya. Singkatnya: Anda perlu mendefinisikan satu metode lagi, sangat mirip -fetchedResultsControllerdengan predikat gabungan khusus.

Dalam kasus pribadi saya, -fetchedResultsControllerpenampilan saya seperti ini:

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

Seperti yang Anda lihat, saya mengambil klien dari satu agensi yang difilter menurut agency.server_idpredikat. Akibatnya saya mengambil konten saya di tableView(semua yang berhubungan dengan implementasi tableViewdan fetchedResultsControllerkode cukup standar) juga. Untuk mengimplementasikan searchFieldsaya mendefinisikan UISearchBarDelegatemetode delegasi. Saya memicunya dengan metode pencarian, katakan -reloadTableView:

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

dan tentu saja definisi -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];
}

Kumpulan kode ini sangat mirip dengan yang pertama, "standar" -fetchedResultsController TETAPI pernyataan if-else di sini adalah:

+andPredicateWithSubpredicates: - menggunakan metode ini kita dapat menetapkan predikat untuk menyimpan hasil pengambilan pertama utama kita di tableView

+orPredicateWithSubpredicates - menggunakan metode ini, kami memfilter pengambilan yang ada dengan permintaan pencarian dari searchBar

Pada akhirnya saya menetapkan array predikat sebagai predikat gabungan untuk pengambilan khusus ini. DAN untuk predikat yang diperlukan, ATAU untuk opsional.

Dan itu saja! Anda tidak perlu menerapkan apa pun lagi. Selamat coding!

Alex
sumber
5

Apakah Anda menggunakan pencarian langsung?

Jika Anda TIDAK, Anda mungkin menginginkan sebuah array (atau NSFetchedResultsController) dengan pencarian sebelumnya yang Anda gunakan, ketika pengguna menekan "search", Anda memberi tahu FetchedResult Anda untuk mengubah predikatnya.

Apa pun itu, Anda harus membangun kembali Hasil Fetched Anda setiap saat. Saya sarankan menggunakan hanya satu NSFetchedResultsController, karena Anda harus menduplikasi kode Anda banyak dan Anda tidak perlu membuang memori dalam sesuatu yang tidak Anda tampilkan.

Pastikan Anda memiliki variabel "searchParameters" NSString dan metode FetchedResults membangunnya kembali untuk Anda sesuai kebutuhan, menggunakan parameter pencarian jika tersedia, Anda harus melakukan:

a) atur "searchParameters" menjadi sesuatu (atau nihil, jika Anda menginginkan semua hasil).

b) lepaskan dan setel ke nol objek NSFetchedResultsController saat ini.

c) memuat kembali data tabel.

Ini kode sederhana:

- (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;    
}
Sergio Moura
sumber
Sangat menarik! Saya akan mencobanya.
jschmidt
Ini tampaknya berfungsi tetapi gagal ketika tabel Anda diisi oleh FRC, searchTableView adalah tabel yang berbeda dari tampilan tabel utama yang Anda gunakan. Metode delegasi FRC memuntahkan semua tempat ketika pada perangkat dengan memori rendah saat mencari dan tableView utama ingin memuat ulang sel.
Brent Priddy
Adakah yang punya tautan ke templat proyek untuk ini? Saya merasa sangat sulit mencari tahu apa yang terjadi. Akan sangat baik memiliki templat yang berfungsi sebagai referensi.
RyeMAC3
@Brent, Anda harus memeriksa apakah itu tampilan tabel pencarian yang membutuhkan perubahan dalam metode delegasi FRC - jika Anda melakukan dan memperbarui tabel yang benar dalam metode delegasi FRC dan UITableView maka semuanya akan baik-baik saja ketika menggunakan FRC untuk tampilan tabel utama dan cari tampilan tabel.
Kervich
@kervich Saya yakin Anda menggambarkan jawaban saya di atas atau apakah Anda mengatakan Anda dapat melakukannya hanya dengan satu FRC?
Brent Priddy
5

Swift 3.0, UISearchController, NSFetchedResultsController dan Data Inti

Kode ini akan berfungsi di Swift 3.0 with Core Data! Anda akan memerlukan metode delegasi tunggal dan beberapa baris kode untuk memfilter dan mencari objek dari model. Tidak ada yang diperlukan jika Anda telah menerapkan semua FRCdan delegatemetode mereka jugasearchController .

The UISearchResultsUpdatingMetode Protokol

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

Itu dia! Semoga ini bisa membantu Anda! Terima kasih

Mannopson
sumber
1

SWIFT 3.0

Gunakan textField, UISearchDisplayController sudah usang pada iOS 8, Anda harus menggunakan UISearchController. Alih-alih berurusan dengan Kontroler Pencarian, mengapa Anda tidak membuat mekanisme pencarian Anda sendiri? Anda dapat menyesuaikannya lebih banyak dan memiliki kontrol lebih besar terhadapnya, dan tidak perlu khawatir tentang perubahan SearchController dan / atau tidak digunakan lagi.

Metode ini saya gunakan bekerja dengan sangat baik, dan tidak memerlukan banyak kode. Itu mengharuskan Anda menggunakan Data Inti dan mengimplementasikan NSFetchedResultsController, namun.

Pertama, buat TextField dan daftarkan dengan metode:

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

Kemudian buat metode textFieldDidChange Anda, dijelaskan dalam pemilih ketika target ditambahkan:

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

Kemudian Anda ingin memfilter daftar dalam filterList()metode menggunakan NSPredicate atau NSCompound predikat jika lebih kompleks. Dalam metode filterList saya, saya memfilter berdasarkan nama entitas, dan nama entitas "subKategori" objek (hubungan satu ke banyak).

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")
            }
        }
    }
}
Josh O'Connor
sumber
0

Saya pikir Luka memiliki pendekatan yang lebih baik untuk ini. Lihat LargeDataSetSample dan alasannya

Dia tidak menggunakan FetchedResultsController, tetapi menggunakan cache saat mencari, karenanya hasil pencarian tampak jauh lebih cepat ketika pengguna mengetik lebih banyak di SearchBar

Saya telah menggunakan pendekatannya di aplikasi saya dan berfungsi dengan baik. Ingat juga jika Anda ingin bekerja dengan objek Model, buat sesederhana mungkin, lihat jawaban saya tentang setPropertiesToFetch

onmyway133
sumber
0

Inilah cara menangani fetchedResult dengan beberapa set data yang sederhana dan cukup umum untuk diterapkan hampir di mana saja. Cukup ambil hasil utama Anda ke array ketika beberapa kondisi hadir.

NSArray *results = [self.fetchedResultsController fetchedObjects];

Permintaan array dengan mengulanginya atau apa pun yang Anda inginkan untuk membuat subset dari hasil utama yang diambil. Dan sekarang Anda dapat menggunakan set lengkap atau subset ketika beberapa kondisi hadir.

smileBot
sumber
0

Saya sangat menyukai pendekatan @Josh O'Connor di mana dia tidak menggunakan UISearchController. Pengontrol ini masih (Xcode 9) memiliki bug tata letak yang banyak mencoba untuk mengatasinya.

Saya kembali menggunakan UISearchBarbukan UITextField, dan itu bekerja dengan cukup baik. Persyaratan saya untuk pencarian / filter adalah untuk menghasilkan NSPredicate. Ini diteruskan ke 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

Akhirnya, pasang SearchBar ke delegasinya.

Saya harap ini membantu orang lain

Si kakek tua
sumber
0

Pendekatan Sederhana untuk Memfilter UITableView yang ada menggunakan CoreData dan yang sudah diurutkan bagaimana Anda inginkan.

Ini benar-benar juga saya 5 menit untuk mengatur dan mulai bekerja.

Saya sudah UITableViewmenggunakan menggunakan CoreDatadiisi dengan Data dari iCloud dan yang memiliki interaksi pengguna yang cukup rumit dan saya tidak ingin harus meniru semua itu untuk UISearchViewController. Saya bisa dengan mudah menambahkan predikat ke yang FetchRequestsudah digunakan oleh FetchResultsControllerdan yang menyaring data yang sudah diurutkan.

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