NSFetchedResultsController (CoreData) UISearchDisplayController / UISearchBar ile nasıl filtrelenir?


146

CoreData tabanlı iPhone uygulamamda arama kodunu uygulamaya çalışıyorum. Nasıl devam edeceğimi bilmiyorum. Uygulama zaten birincil TableView için verileri almak için bir koşula sahip bir NSFetchedResultsController'a sahiptir. Çok fazla kod değiştirmeden önce doğru yolda olduğumdan emin olmak istiyorum. Kafam karıştı çünkü pek çok örnek CoreData yerine dizi tabanlı.

İşte bazı sorular:

  1. Yalnızca eşleşen öğeleri alan ikinci bir NSFetchedResultsController'a sahip olmam gerekir mi yoksa birincil TableView ile aynı olanı kullanabilir miyim?

  2. Aynısını kullanırsam, FRC önbelleğini temizlemek ve ardından handleSearchForTerm: searchString yöntemindeki koşulu değiştirmek kadar basit mi? Doğrulamanın arama terimlerinin yanı sıra başlangıç ​​yüklemini de içermesi gerekiyor mu yoksa ilk etapta verileri almak için bir yüklem kullandığını hatırlıyor mu?

  3. Orijinal sonuçlara nasıl geri dönebilirim? Arama koşulunu sıfır olarak mı ayarlamalıyım? Bu, en başta FRC sonuçlarını almak için kullanılan orijinal koşulu öldürmez mi?

FRC ile aramayı kullanan herhangi bir kod örneği varsa, çok memnun olurum!


@Brent, mükemmel çözüm, benim için bir muamele yaptı!
DetartrateD

Yanıtlar:


193

Aslında bunu projelerimden birinde uyguladım (sorunuz ve diğer yanlış cevap ne yapacağımı ima etti). Sergio'nun cevabını denedim ama aslında bir cihazda çalışırken istisna sorunları yaşadım.

Evet, iki getirme sonucu denetleyicisi oluşturursunuz: biri normal görüntü için ve diğeri UISearchBar'ın tablo görünümü için.

Yalnızca bir FRC (NSFetchedResultsController) kullanırsanız, orijinal UITableView (arama sırasında etkin olan arama tablosu görünümü değil) muhtemelen arama yaparken çağrılan geri aramalara sahip olacak ve FRC'nizin filtrelenmiş sürümünü yanlış kullanmaya çalışacak ve istisnalar göreceksiniz. bölümlerdeki yanlış sayıda bölüm veya satır hakkında atılır.

İşte yaptığım şey: fetchedResultsController ve searchFetchedResultsController özellikleri olarak iki FRC var. SearchFetchedResultsController, bir arama olmadığı sürece kullanılmamalıdır (arama iptal edildiğinde aşağıda bu nesnenin serbest bırakıldığını görebilirsiniz). Tüm UITableView yöntemleri, hangi tablo görünümünü sorgulayacağını ve bilgiyi hangi FRC'den çekeceğini bulmalıdır. FRC temsilci yöntemleri de hangi tableView'in güncelleneceğini bulmalıdır.

Bunun ne kadarının ortak kod olması şaşırtıcı.

Başlık dosyasının ilgili bitleri:

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

uygulama dosyasının ilgili bitleri:

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

Tüm UITableViewDelegate / DataSource yöntemleriyle çalışırken doğru FRC'yi almak için yararlı bir yöntem oluşturdum:

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

}

Arama çubuğu için temsilci yöntemleri:

#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 temsilci yöntemlerinden güncellemeler alırken doğru tablo görünümünü kullandığınızdan emin olun:

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

Diğer görüntüleme bilgileri:

- (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 oluşturma kodu:

- (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
Bu çok güzel çalışıyor gibi görünüyor! Teşekkürler Brent! Özellikle fetchedResultsControllerForTableView: yöntemini seviyorum. Bu onu çok kolaylaştırır!
jschmidt

2
Gülünç derecede iyi kod. Jschmidt'in dediği gibi, özel "fetchedResultsControllerForTableView:" yöntemi tüm süreci gerçekten basitleştirir.
Daniel Amitay

Brent. Adamsın. Ama işte size yeni bir meydan okuma. Arka planda işlemeyi kullanarak bu kodun uygulanması. Uygulamamın diğer bölümlerinde bazı küçük çoklu iş parçacığı yaptım, ancak bu zor (en azından benim için). Daha güzel bir kullanıcı deneyimi katacağını düşünüyorum. Meydan okuma kabul edildi?
jschmidt

3
@BrentPriddy Teşekkürler! Ben kodunuzu refactored Değiştir Getirme İsteği yerine ayarlama searchFetchedResultsControlleriçin nilher zaman arama metin değişiklikleri.
ma11hew28

2
Gözlerinde farklı cellForRowAtIndexPath, içinden hücreyi almak olmamalıdır self.tableViewbiri sivri gibi bu SO soru? Bunu yapmazsanız, özel hücre görüntülenmez.
amb

18

Bazıları bunun bir single ile yapılabileceği yorumunu yaptı NSFetchedResultsController. Ben de öyle yaptım ve işte detaylar. Bu çözüm, yalnızca tabloyu filtrelemek ve arama sonuçlarının diğer tüm özelliklerini (sıralama düzeni, hücre düzeni vb.) Korumak istediğinizi varsayar.

Öncelikle, UITableViewControlleralt sınıfınızda iki özellik tanımlayın (varsa uygun @synthesize ve dealloc ile):

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

İkinci olarak, alt sınıfınızın viewDidLoad:yönteminde arama çubuğunu başlatın 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];

Üçüncüsü, aşağıdaki UISearchDisplayControllergibi temsilci yöntemlerini uygulayın :

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

Son olarak, fetchedResultsControlleryöntemde tanımlanmışsa NSPredicatebağlı olarak değiştirin 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
Bu çözüm benim için iyi çalıştı ve çok daha basit. Teşekkürler! Yalnızca 'if (self.searchString)' ile 'if (self.searchString.length) arasında ince ayar yapmanızı öneririm. Bu, bir arama başlattıktan ve dizeyi arama çubuğundan sildikten sonra tablo görünümüne tıklarsanız çökmesini önler.
Guto Araujo

17

Bunu çalıştırmak için birkaç denemem gerekti ...

Anlamak için anahtarım, burada iş başında olan iki tablo görünümü olduğunu fark etmekti. Biri görüntü denetleyicim tarafından yönetiliyor ve diğeri arama görüntü denetleyicisi tarafından yönetiliyor ve sonra hangisinin aktif olduğunu ve doğru şeyi yaptığını görmek için test edebildim. Dokümantasyon da yardımcı oldu:

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

İşte yaptığım şey -

SearchIsActive bayrağı eklendi:

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

Uygulama dosyasına sentezleme eklendi.

Daha sonra arama için şu yöntemleri ekledim:

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

Sonra controllerWillChangeContent içinde:

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

Ve controllerDidChangeContent:

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

Ve koşulu sıfırlarken önbelleği silin.

Bu yardımcı olur umarım.


yine de anlamıyorum, yukarıdaki örnek çok iyi, ancak eksik, ancak tavsiyeniz işe
yaramalı

BOOL kullanmak yerine aktif tablo görünümünü kontrol edebilirsiniz:if ( [self.tableView isEqual:self.searchDisplayController.searchResultsTableView] ) { ... }
rwyland

@rwyland - Testim, arama etkinken self.tableview'un searchdisplaycontroller.searchresultstableview'a ayarlanmadığını gösteriyor. Bunlar asla eşit olmayacak.
2013

5

Aynı görevle karşı karşıya kaldım ve onu çözmek için MÜMKÜN EN BASİT YOLU buldum . Kısaca:-fetchedResultsController Özel bir bileşik koşula çok benzer bir yöntem daha tanımlamanız gerekir .

Kişisel durumumda benim -fetchedResultsController şuna benziyor:

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

Gördüğünüz gibi, koşula göre filtrelenmiş bir ajansın müşterilerini alıyorum agency.server_id. Sonuç olarak, içeriğimi de alıyorum tableView(tümünün uygulanmasıyla ilgili tableViewve fetchedResultsControllerkod oldukça standart). Uygulamak için searchFieldbir tanımlıyorumUISearchBarDelegate temsilci yöntemi . Arama yöntemiyle tetikliyorum, şunu söyle -reloadTableView:

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

ve tabii ki tanımı -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];
}

Bu kod grubu ilkine çok benzer, "standart" -fetchedResultsController AMA burada if-else ifadesinin içinde:

+andPredicateWithSubpredicates: - bu yöntemi kullanarak, ana ilk getirmemizin sonuçlarını, tableView

+orPredicateWithSubpredicates - bu yöntemi kullanarak, mevcut getirmeyi arama sorgusuna göre filtreliyoruz. searchBar

Sonunda, bu belirli getirme için bir bileşik yüklem olarak yüklemler dizisini ayarlıyorum. VE gerekli yüklemler için VEYA isteğe bağlı olarak.

Ve hepsi bu! Daha fazla bir şey uygulamanıza gerek yok. Mutlu kodlamalar!


5

Canlı arama kullanıyor musunuz?

DEĞİLSENİZ, muhtemelen kullandığınız önceki aramalarla bir dizi (veya bir NSFetchedResultsController) istersiniz, kullanıcı "arama" tuşuna bastığında, FetchedResults'a yüklemini değiştirmesini söylersiniz.

Her iki durumda da, FetchedResult'larınızı her seferinde yeniden oluşturmanız gerekecektir. Yalnızca bir NSFetchedResultsController kullanmanızı tavsiye ederim, çünkü kodunuzu çok fazla çoğaltmanız gerekecek ve göstermediğiniz bir şeyde bellek harcamanıza gerek kalmayacak.

Sadece bir NSString "searchParameters" değişkenine sahip olduğunuzdan ve FetchedResults yönteminizin bunu sizin için gerektiğinde yeniden oluşturduğundan emin olun, eğer varsa arama parametrelerini kullanarak yapmanız yeterlidir:

a) "searchParameters" değerini bir şeye (veya tüm sonuçları istiyorsanız sıfıra) ayarlayın.

b) geçerli NSFetchedResultsController nesnesini serbest bırakın ve sıfıra ayarlayın.

c) tablo verilerini yeniden yükleyin.

İşte basit bir kod:

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

Çok ilginç! Bunu bir deneyeceğim.
jschmidt

Bu işe yarıyor gibi görünüyor, ancak tablonuz FRC tarafından doldurulduğunda başarısız oluyor, searchTableView, kullandığınız ana tablo görünümünden farklı bir tablodur. FRC delege yöntemleri, arama sırasında düşük belleğe sahip bir cihazdayken ve ana tableView hücreleri yeniden yüklemek istediğinde her yere kusar.
Brent Priddy

Bunun için bir proje şablonuna bağlantısı olan var mı? Neyin nereye gittiğini anlamakta gerçekten zorlanıyorum. Referans olarak bir çalışma şablonuna sahip olmak çok güzel olurdu.
RyeMAC3

@Brent, FRC temsilci yöntemlerinde değişiklik gerektiren arama tablosu görünümünün bu olup olmadığını kontrol etmelisiniz - FRC ve UITableView temsilcilerinin yöntemlerinde doğru tabloyu yapıp güncellerseniz, hem ana tablo görünümü hem de FRC'yi kullanırken her şey yolunda olmalıdır. arama tablosu görünümü.
kervich

@kervich Cevabımı yukarıda açıkladığına inanıyorum veya bunu sadece bir FRC ile yapabileceğini mi söylüyorsun?
Brent Priddy

5

Swift 3.0, UISearchController, NSFetchedResultsController ve Core Data

Bu kod ile Swift 3.0'da çalışacak Core Data! Modelden nesneleri filtrelemek ve aramak için tek bir temsilci yöntemine ve birkaç satır koda ihtiyacınız olacak. Tüm FRCve delegateyöntemlerini uyguladıysanız hiçbir şeye ihtiyaç duyulmayacaktır searchController.

UISearchResultsUpdatingprotokol yöntemi

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

Bu kadar! Umarım size yardımcı olur! Teşekkürler


1

SWIFT 3.0

Bir textField kullanın, UISearchDisplayController iOS 8'den itibaren kullanımdan kaldırılmıştır, bir UISearchController kullanmanız gerekir. Arama Kontrolörü ile uğraşmak yerine, neden kendi arama mekanizmanızı oluşturmuyorsunuz? Daha fazla özelleştirebilir ve üzerinde daha fazla kontrole sahip olabilirsiniz ve SearchController'ın değişmesi ve / veya kullanımdan kaldırılması konusunda endişelenmenize gerek kalmaz.

Kullandığım bu yöntem çok iyi çalışıyor ve fazla kod gerektirmiyor. Ancak Core Data kullanmanızı ve NSFetchedResultsController'ı uygulamanızı gerektirir.

İlk olarak, bir TextField oluşturun ve bir yöntemle kaydedin:

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

Ardından, hedef eklendiğinde seçicide açıklanan textFieldDidChange yönteminizi oluşturun:

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

Ardından filterList(), daha karmaşıksa, NSPredicate veya NSCompound yüklemini kullanarak yöntemdeki listeyi filtrelemek istiyorsunuz . FilterList yöntemimde, varlığın adına ve varlıkların "subCategories" nesnesinin adına (bire çok ilişki) göre filtreleme yapıyorum.

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

Luka'nın bunun için daha iyi bir yaklaşımı olduğunu düşünüyorum. LargeDataSetSample ve nedenini görün

O kullanmıyor FetchedResultsController , ancak arama yaparken önbellek kullanır, bu nedenle kullanıcı SearchBar'da daha fazla yazdığında arama sonuçları çok daha hızlı görünür.

Yaklaşımını uygulamamda kullandım ve iyi çalışıyor. Ayrıca Model nesnesiyle çalışmak istiyorsanız, bunu olabildiğince basitleştirin, setPropertiesToFetch hakkındaki cevabıma bakın.


0

Burada, hem basit hem de hemen hemen her yere uygulanabilecek kadar genel olan birden çok veri kümesiyle getirilen Sonuçları işlemenin bir yolu. Bir koşul mevcut olduğunda ana sonuçlarınızı bir diziye aktarmanız yeterlidir.

NSArray *results = [self.fetchedResultsController fetchedObjects];

Ana fetchedResults'ınızın bir alt kümesini oluşturmak için diziyi veya istediğiniz her şeyi döngü yaparak sorgulayın. Ve şimdi, bazı koşullar mevcut olduğunda tam seti veya alt kümeyi kullanabilirsiniz.


0

@ Josh O'Connor'ın a UISearchController. Bu denetleyicide hala (Xcode 9), birçoğunun geçici olarak çözmeye çalıştığı bir düzen hatası var.

A UISearchBaryerine a kullanmaya döndüm UITextFieldve oldukça iyi çalışıyor. Arama / filtre ihtiyacım bir NSPredicate. Bu FRC'ye iletilir:

   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

Son olarak, SearchBar'ı temsilcisine bağlayın.

Umarım bu diğerlerine yardım eder


0

CoreData'yı kullanarak mevcut UITableView'i filtrelemek için basit bir yaklaşım ve hangisi istediğiniz gibi zaten sıralanmış.

Bu gerçekten de kurulum ve çalışmaya başlamak için 5 dakika.

Bir varolan vardı UITableViewkullanarak CoreDataiCloud'dan Verilerle doldurulur ve oldukça kullanıcı etkileşimlerini komplike olan ve o kadar bir için çoğaltmak zorunda kalmak istemiyordu UISearchViewController. FetchRequestZaten tarafından kullanılan FetchResultsControllerve önceden sıralanmış verileri filtreleyen mevcut olana basitçe bir yüklem ekleyebildim .

-(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];
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.