วิธีการกรอง NSFetchedResultsController (CoreData) ด้วย UISearchDisplayController / UISearchBar


146

ฉันกำลังพยายามใช้รหัสการค้นหาในแอพ CoreData บน iPhone ของฉัน ฉันไม่แน่ใจว่าจะดำเนินการต่อไปอย่างไร แอพมี NSFetchedResultsController พร้อมกับภาคแสดงเพื่อดึงข้อมูลสำหรับ TableView หลักแล้ว ฉันต้องการให้แน่ใจว่าฉันอยู่บนเส้นทางที่ถูกต้องก่อนที่ฉันจะเปลี่ยนรหัสมากเกินไป ฉันสับสนเพราะตัวอย่างจำนวนมากเป็นแบบอาเรย์แทนที่จะเป็น CoreData

นี่คือคำถามบางส่วน:

  1. ฉันต้องมี NSFetchedResultsController อันที่สองที่ดึงข้อมูลเฉพาะรายการที่ตรงกันหรือฉันสามารถใช้อันเดียวกับ TableView หลักได้หรือไม่

  2. ถ้าฉันใช้อันเดียวกันมันง่ายเหมือนการล้างแคช FRC แล้วเปลี่ยนเพรดิเคตใน handleSearchForTerm: searchString? เพรดิเคตต้องมีเพรดิเคตเริ่มต้นเช่นเดียวกับคำค้นหาหรือจำได้ว่ามันใช้เพรดิเคตเพื่อดึงข้อมูลในตอนแรกหรือไม่

  3. ฉันจะกลับไปที่ผลลัพธ์ดั้งเดิมได้อย่างไร ฉันเพิ่งตั้งค่าเพรดิเคตการค้นหาเป็นศูนย์หรือไม่ จะไม่ฆ่าเพรดิเคตดั้งเดิมที่ใช้เพื่อเรียกผลลัพธ์ FRC ในตอนแรกหรือไม่

หากใครมีตัวอย่างของรหัสโดยใช้การค้นหาด้วย FRC ฉันจะขอบคุณมันมาก!


@Brent โซลูชั่นที่สมบูรณ์แบบทำงานเพื่อฉัน!
DetartrateD

คำตอบ:


193

จริง ๆ แล้วฉันเพิ่งใช้สิ่งนี้กับหนึ่งในโครงการของฉัน (คำถามของคุณและคำตอบที่ผิดอื่น ๆ ที่บอกใบ้ถึงสิ่งที่ต้องทำ) ฉันลองคำตอบของ Sergio แต่มีปัญหาข้อยกเว้นเมื่อทำงานบนอุปกรณ์จริง

ใช่คุณสร้างตัวควบคุมผลลัพธ์การดึงข้อมูลสองรายการ: รายการหนึ่งสำหรับการแสดงผลปกติและอีกรายการหนึ่งสำหรับมุมมองตารางของ 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:" แบบกำหนดเองทำให้กระบวนการทั้งหมดง่ายขึ้น
Daniel Amitay

เบรนท์ คุณคือผู้ชาย แต่นี่คือความท้าทายใหม่สำหรับคุณ การใช้รหัสนี้โดยใช้การประมวลผลพื้นหลัง ฉันได้ทำเธรดหลายส่วนเล็ก ๆ ของแอพของฉันมาบ้างแล้ว แต่นี่เป็นเรื่องที่ยาก (สำหรับฉันอย่างน้อย) ฉันคิดว่ามันจะเพิ่มประสบการณ์การใช้งานที่ดีกว่า รับคำท้า?
jschmidt

3
@BrentPriddy ขอบคุณ! ฉัน refactored รหัสของคุณที่จะปรับเปลี่ยนคำขอดึงข้อมูลแทนการตั้งค่าsearchFetchedResultsControllerไปnilทุกครั้งที่มีการเปลี่ยนแปลงข้อความค้นหา
ma11hew28

2
ในของคุณคุณcellForRowAtIndexPathไม่ควรได้รับเซลล์จากself.tableViewใครบางคนชี้ไปที่คำถาม SO นี้หรือไม่? หากคุณไม่ทำเช่นนี้เซลล์ที่กำหนดเองจะไม่ปรากฏขึ้น
amb

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)' ถึง 'if (self.searchString.length) สิ่งนี้จะช่วยป้องกันไม่ให้ระบบล่มเมื่อคุณคลิกที่มุมมองตารางหลังจากเริ่มการค้นหาและลบสตริงออกจากแถบค้นหา
Guto Araujo

17

ฉันใช้ความพยายามสองสามครั้งเพื่อทำงานนี้ ...

กุญแจสำคัญในการทำความเข้าใจของฉันคือการตระหนักว่ามีสองมุมมองที่ทำงานอยู่ที่นี่ หนึ่งจัดการโดย viewcontroller ของฉันและอีกจัดการโดย searchviewcontroller แล้วฉันสามารถทดสอบเพื่อดูว่ามีการใช้งานและทำสิ่งที่ถูกต้อง เอกสารก็มีประโยชน์เช่นกัน:

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

นี่คือสิ่งที่ฉันทำ -

เพิ่มการตั้งค่าสถานะ 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;

เพิ่มการสังเคราะห์ในไฟล์การนำไปใช้งาน

จากนั้นฉันได้เพิ่มวิธีการเหล่านี้เพื่อการค้นหา:

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

และตัวควบคุมวันเปลี่ยนเนื้อหา:

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

และลบแคชเมื่อทำการรีเซ็ตเพรดิเคต

หวังว่านี่จะช่วยได้


แต่ฉันไม่เข้าใจตัวอย่างข้างต้นที่ดีมาก แต่ไม่สมบูรณ์ แต่คำแนะนำของคุณควรจะทำงาน แต่ก็ไม่ได้ ...
วลาดิเมีย Stazhilov

คุณสามารถตรวจสอบมุมมองตารางที่แอ็คทีฟแทนการใช้ 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 แต่แรกในคำสั่ง if-else ที่นี่คือ:

+andPredicateWithSubpredicates: - การใช้วิธีนี้เราสามารถตั้งค่าเพรดิเคตเพื่อบันทึกผลลัพธ์ของการดึงข้อมูลหลักครั้งแรกใน tableView

+orPredicateWithSubpredicates - การใช้วิธีนี้เรากำลังกรองการดึงข้อมูลที่มีอยู่โดยการค้นหาจาก searchBar

ในตอนท้ายฉันตั้งค่าเพรดิเคตเป็นเพรดิเคตสารประกอบสำหรับการดึงข้อมูลนี้โดยเฉพาะ และสำหรับภาคแสดงที่ต้องการหรือสำหรับตัวเลือก

และนั่นคือทั้งหมด! คุณไม่จำเป็นต้องใช้อะไรเพิ่มเติม การเข้ารหัสมีความสุข!


5

คุณใช้การค้นหาออนไลน์หรือไม่?

หากคุณไม่ใช่คุณอาจต้องการอาร์เรย์ (หรือ NSFetchedResultsController) ที่มีการค้นหาก่อนหน้านี้ที่คุณใช้เมื่อผู้ใช้กด "ค้นหา" คุณจะบอก FetchedResults ของคุณเพื่อเปลี่ยนเพรดิเคต

ไม่ว่าจะด้วยวิธีใดคุณจะต้องสร้าง FetchedResults ใหม่ทุกครั้ง ฉันขอแนะนำให้ใช้ NSFetchedResultsController เพียงอันเดียวเนื่องจากคุณจะต้องทำซ้ำรหัสของคุณมากมายและคุณไม่จำเป็นต้องเสียความทรงจำในสิ่งที่คุณไม่ได้แสดง

เพียงตรวจสอบให้แน่ใจว่าคุณมีตัวแปร "searchParameters" ของ NSString และวิธีการ FetchedResults ของคุณจะสร้างขึ้นมาใหม่ตามต้องการโดยใช้พารามิเตอร์การค้นหาถ้ามีคุณควรทำดังนี้

ก) ตั้งค่า "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 สำหรับทั้งมุมมองหลักและ ค้นหา tableview
kervich

@ kervich ฉันเชื่อว่าคุณกำลังอธิบายคำตอบของฉันไว้ด้านบนหรือคุณกำลังบอกว่าคุณสามารถทำได้ด้วย FRC เพียงอันเดียว?
Brent Priddy

5

Swift 3.0, UISearchController, NSFetchedResultsController และ Core Data

รหัสนี้จะทำงานกับ Swift 3.0 ด้วยCore Data! คุณจะต้องใช้วิธีการมอบหมายเพียงครั้งเดียวและโค้ดสองสามบรรทัดสำหรับการกรองและค้นหาวัตถุจากโมเดล ไม่มีอะไรที่จะต้องถ้าคุณได้ดำเนินการทั้งหมดของFRCพวกเขาและวิธีการเช่นเดียวกับdelegatesearchController

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 และ / หรือเลิกใช้งาน

วิธีนี้ฉันใช้งานได้ดีมากและไม่ต้องการรหัสมาก มันต้องการให้คุณใช้ Core Data และนำ NSFetchedResultsController มาใช้

ขั้นแรกให้สร้างฟิลด์ข้อความและลงทะเบียนด้วยเมธอด:

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 ของฉันฉันกำลังกรองตามชื่อของเอนทิตีและชื่อของวัตถุ "subCategories" ของเอนทิตี (ความสัมพันธ์แบบหนึ่งต่อหลาย)

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 มากขึ้น

ฉันใช้วิธีการของเขาในแอปของฉันและใช้งานได้ดี โปรดจำไว้ว่าถ้าคุณต้องการทำงานกับโมเดลวัตถุทำให้มันง่ายที่สุดดูคำตอบของฉันเกี่ยวกับsetPropertiesToFetch


0

ต่อไปนี้เป็นวิธีจัดการ fetchedResults ด้วยชุดข้อมูลหลายชุดที่เรียบง่ายและทั่วไปพอที่จะนำไปใช้ได้เกือบทุกที่ เพียงหยิบผลลัพธ์หลักของคุณไปยังอาร์เรย์เมื่อมีเงื่อนไขบางอย่างเกิดขึ้น

NSArray *results = [self.fetchedResultsController fetchedObjects];

ค้นหาอาร์เรย์ด้วยการวนซ้ำมันหรือสิ่งที่คุณต้องการเพื่อสร้างชุดย่อยของ fetchedResults หลักของคุณ และตอนนี้คุณสามารถใช้ชุดเต็มหรือชุดย่อยเมื่อมีเงื่อนไขบางอย่าง


0

ผมชอบวิธีการของคอนเนอร์ @ Josh 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];
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.