Cách lọc NSFetchedResultsControll (CoreData) với UISearchDisplayControll / UISearchBar


146

Tôi đang cố gắng triển khai mã tìm kiếm trong ứng dụng iPhone dựa trên CoreData của mình. Tôi không chắc chắn làm thế nào để tiến hành. Ứng dụng này đã có NSFetchedResultsControll với một vị từ để lấy dữ liệu cho TableView chính. Tôi muốn chắc chắn rằng tôi đang đi đúng hướng trước khi tôi thay đổi quá nhiều mã. Tôi bối rối vì rất nhiều ví dụ dựa trên mảng thay vì CoreData.

Dưới đây là một số câu hỏi:

  1. Tôi có cần phải có NSFetchedResultsControll thứ hai chỉ truy xuất các mục phù hợp hay tôi có thể sử dụng cùng một mục với TableView chính không?

  2. Nếu tôi sử dụng cùng một thứ, thì có đơn giản như xóa bộ đệm FRC và sau đó thay đổi vị từ trong phương thức handleSearchForTerm: searchString không? Có phải vị từ phải chứa vị từ ban đầu cũng như các cụm từ tìm kiếm hay nó có nhớ rằng nó đã sử dụng một vị từ để lấy dữ liệu ở vị trí đầu tiên không?

  3. Làm thế nào để tôi lấy lại kết quả ban đầu? Tôi chỉ cần đặt vị ngữ tìm kiếm là không? Không giết vị ngữ ban đầu được sử dụng để lấy kết quả FRC ở vị trí đầu tiên phải không?

Nếu bất cứ ai có bất kỳ ví dụ nào về mã sử dụng tìm kiếm với FRC, tôi sẽ đánh giá rất cao nó!


@Brent, giải pháp hoàn hảo, đã làm việc cho tôi!
Khởi hành

Câu trả lời:


193

Tôi thực sự chỉ thực hiện điều này trên một trong các dự án của tôi (câu hỏi của bạn và câu trả lời sai khác gợi ý những việc cần làm). Tôi đã thử câu trả lời của Sergio nhưng gặp vấn đề ngoại lệ khi thực sự chạy trên thiết bị.

Có, bạn tạo hai bộ điều khiển kết quả tìm nạp: một cho màn hình bình thường và một cho bộ xem bảng của UISearchBar.

Nếu bạn chỉ sử dụng một FRC (NSFetchedResultsCont kiểm) thì UITableView ban đầu (không phải là chế độ xem bảng tìm kiếm đang hoạt động trong khi tìm kiếm) có thể có các cuộc gọi lại được gọi trong khi bạn đang tìm kiếm và cố gắng sử dụng không chính xác phiên bản đã lọc của FRC và bạn sẽ thấy ngoại lệ ném về số lượng phần hoặc hàng không chính xác trong phần.

Đây là những gì tôi đã làm: Tôi có sẵn hai FRC là các thuộc tính fetchedResultsControll và searchFetchedResultsControll. Không nên sử dụng searchFetchedResultsControll trừ khi có tìm kiếm (khi tìm kiếm bị hủy, bạn có thể thấy bên dưới đối tượng này được phát hành). Tất cả các phương thức UITableView phải tìm ra chế độ xem bảng nào nó sẽ truy vấn và FRC nào có thể áp dụng để lấy thông tin từ đó. Các phương thức ủy nhiệm của FRC cũng phải tìm ra bảngView nào cần cập nhật.

Thật đáng ngạc nhiên bao nhiêu trong số này là mã soạn sẵn.

Các bit có liên quan của tệp tiêu đề:

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

bit liên quan của tệp thực hiện:

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

Tôi đã tạo một phương thức hữu ích để truy xuất FRC chính xác khi làm việc với tất cả các phương thức 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;

}

Các phương thức ủy nhiệm cho thanh tìm kiếm:

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

đảm bảo rằng bạn sử dụng chế độ xem bảng chính xác khi nhận các bản cập nhật từ các phương thức ủy nhiệm của 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];
}

Thông tin xem khác:

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

Mã tạo 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
Điều đó dường như làm việc rất đẹp! Cảm ơn, Brent! Tôi đặc biệt thích phương thức fetchedResultsControllForTableView :. Điều đó làm cho nó rất dễ dàng!
jschmidt

2
Vô lý mã tốt. Như jschmidt đã nói, phương thức "fetchedResultsControllForTableView" tùy chỉnh thực sự đơn giản hóa toàn bộ quá trình.
Daniel Amitay

Dầu thô. Bạn là người đàn ông. Nhưng, đây là một thách thức mới cho bạn. Thực hiện mã này bằng cách xử lý nền. Tôi đã thực hiện một số đa luồng nhỏ của các phần khác trong ứng dụng của mình, nhưng điều này rất khó khăn (ít nhất là đối với tôi). Tôi nghĩ rằng nó sẽ thêm một trải nghiệm người dùng đẹp hơn. Thử thách được chấp nhận?
jschmidt

3
@BrentPriddy Cảm ơn! Tôi đã cấu trúc lại mã của bạn để Sửa đổi Yêu cầu tìm nạp thay vì đặt searchFetchedResultsControllerthành nilmỗi khi văn bản tìm kiếm thay đổi.
ma11hew28

2
Trong bạn cellForRowAtIndexPath, bạn không nên lấy tế bào self.tableViewgiống như ai đó đã chỉ ra trong câu hỏi SO này ? Nếu bạn không làm điều này, ô tùy chỉnh sẽ không được hiển thị.
amb

18

Một số đã nhận xét rằng điều này có thể được thực hiện với một NSFetchedResultsController. Đó là những gì tôi đã làm, và đây là chi tiết. Giải pháp này giả định rằng bạn chỉ muốn lọc xuống bảng và duy trì tất cả các khía cạnh khác (thứ tự sắp xếp, bố cục ô, v.v.) của kết quả tìm kiếm.

Đầu tiên, xác định hai thuộc tính trong UITableViewControllerlớp con của bạn (với @synthesize và dealloc thích hợp, nếu có):

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

Thứ hai, khởi tạo thanh tìm kiếm trong viewDidLoad:phương thức của UITableViewControllerlớp con của bạn :

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

Thứ ba, thực hiện các UISearchDisplayControllerphương thức đại biểu như thế này:

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

Cuối cùng, trong fetchedResultsControllerphương thức thay đổi NSPredicatetùy thuộc nếu self.searchStringđược định nghĩa:

-(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
Giải pháp này hiệu quả với tôi và nó đơn giản hơn nhiều. Cảm ơn! Tôi chỉ đề xuất điều chỉnh 'if (self.searchString)' thành 'if (self.searchString.length). Điều này ngăn nó bị sập nếu bạn nhấp vào chế độ xem bảng sau khi bắt đầu tìm kiếm và xóa chuỗi khỏi thanh tìm kiếm.
Guto Araujo

17

Phải mất một vài lần tôi mới có thể làm việc này ...

Chìa khóa để hiểu của tôi là nhận ra rằng có hai bảng xem đang hoạt động ở đây. Một được quản lý bởi trình điều khiển khung nhìn của tôi và một được quản lý bởi trình điều khiển tìm kiếm và sau đó tôi có thể kiểm tra xem cái nào đang hoạt động và thực hiện đúng. Các tài liệu cũng hữu ích:

http://developer.apple.com/l Library / ios /#documentation / uikit / report / UISearchDisplayContoder_Class / Reference / Reference.html

Đây là những gì tôi đã làm -

Đã thêm cờ 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;

Đã thêm tổng hợp trong tệp thực hiện.

Sau đó, tôi đã thêm các phương thức này để tìm kiếm:

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

Sau đó, trong bộ điều khiểnWillChangeContent:

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

Và controlDidChangeContent:

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

Và xóa bộ nhớ cache khi đặt lại vị ngữ.

Hi vọng điêu nay co ich.


Tuy nhiên, tôi không hiểu, ví dụ trên rất tốt, nhưng không đầy đủ, nhưng đề xuất của bạn nên hoạt động, nhưng nó không ...
Vladimir Stazhilov

Bạn chỉ có thể kiểm tra chế độ xem bảng đang hoạt động thay vì sử dụng BOOL:if ( [self.tableView isEqual:self.searchDisplayController.searchResultsTableView] ) { ... }
rwyland

@rwyland - Thử nghiệm của tôi cho thấy self.tableview không được đặt thành searchdisplaycontroll.searchresulturdyview khi tìm kiếm được kích hoạt. Những điều này sẽ không bao giờ bằng nhau.
giff

5

Tôi đã phải đối mặt với cùng một nhiệm vụ và tìm thấy CÁCH MẠNG ĐƠN GIẢN để giải quyết nó. Tóm lại: bạn cần xác định thêm một phương thức, rất giống -fetchedResultsControllervới một vị từ ghép tùy chỉnh.

Trong trường hợp cá nhân của tôi -fetchedResultsControllertrông như thế này:

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

Như bạn có thể thấy tôi đang tìm nạp khách hàng của một cơ quan được lọc theo agency.server_idvị ngữ. Kết quả là tôi cũng đang truy xuất nội dung của mình tableView(tất cả đều liên quan đến việc triển khai tableViewfetchedResultsControllermã cũng khá chuẩn). Để thực hiện searchFieldtôi đang xác định một UISearchBarDelegatephương thức đại biểu. Tôi đang kích hoạt nó bằng phương pháp tìm kiếm, nói -reloadTableView:

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

và tất nhiên định nghĩa về -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];
}

Nhóm mã này rất giống với câu lệnh đầu tiên, "tiêu chuẩn" -fetchedResultsController NHƯ bên trong câu lệnh if-other ở đây là:

+andPredicateWithSubpredicates: - sử dụng phương pháp này, chúng tôi có thể đặt một vị từ để lưu kết quả của lần tìm nạp đầu tiên chính của chúng tôi trong tableView

+orPredicateWithSubpredicates - sử dụng phương pháp này, chúng tôi đang lọc tìm nạp hiện có bằng truy vấn tìm kiếm từ searchBar

Cuối cùng, tôi thiết lập mảng các vị từ làm vị ngữ ghép cho lần tìm nạp cụ thể này. VÀ cho các vị từ cần thiết, HOẶC cho tùy chọn.

Và đó là tất cả! Bạn không cần phải thực hiện thêm bất cứ điều gì. Chúc mừng mã hóa!


5

Bạn đang sử dụng một tìm kiếm trực tiếp?

Nếu bạn KHÔNG, bạn có thể muốn một mảng (hoặc NSFetchedResultsCont kiểm) với các tìm kiếm trước đó bạn đã sử dụng, khi người dùng nhấn "tìm kiếm", bạn nói với FetchedResults của bạn để thay đổi vị từ của nó.

Dù bằng cách nào, bạn sẽ cần phải xây dựng lại FetchedResults của mình mỗi lần. Tôi khuyên bạn chỉ nên sử dụng một NSFetchedResultsControll, vì bạn sẽ phải sao chép mã của mình rất nhiều và bạn không cần lãng phí bộ nhớ trong những thứ bạn không hiển thị.

Chỉ cần đảm bảo rằng bạn có biến NSString "searchParameter" và phương thức FetchedResults của bạn sẽ xây dựng lại nó cho bạn khi cần, sử dụng các tham số tìm kiếm nếu có, bạn chỉ nên làm:

a) đặt "searchParameter" thành một cái gì đó (hoặc không, nếu bạn muốn tất cả các kết quả).

b) phát hành và được đặt thành không đối tượng NSFetchedResultsControll hiện tại.

c) tải lại dữ liệu bảng.

Đây là một mã đơn giản:

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

Rất thú vị! Tôi sẽ thử.
jschmidt

Điều này có vẻ hoạt động nhưng không thành công khi bảng của bạn được FRC điền vào, searchTableView là một bảng khác với chế độ xem bảng chính mà bạn sử dụng. Các phương thức ủy nhiệm của FRC gây khó chịu ở mọi nơi khi trên một thiết bị có bộ nhớ thấp khi tìm kiếm và bảngView chính muốn tải lại các ô.
Brent Priddy

Bất cứ ai cũng có một liên kết đến một mẫu dự án cho điều này? Tôi đang tìm thấy nó thực sự khó khăn để tìm ra những gì đi đâu. Sẽ rất tốt nếu có một mẫu làm việc như một tài liệu tham khảo.
RyeMAC3

@Brent, Bạn nên kiểm tra xem đó có phải là chế độ xem bảng tìm kiếm cần thay đổi trong các phương thức ủy nhiệm của FRC hay không - nếu bạn thực hiện và cập nhật bảng đúng trong các phương thức của đại biểu FRC và UITableView thì mọi thứ sẽ ổn khi sử dụng FRC cho cả chế độ xem bảng chính và tìm kiếm bảng xem.
kervich

@kervich Tôi tin rằng bạn đang mô tả câu trả lời của tôi ở trên hoặc bạn đang nói rằng bạn có thể làm điều đó chỉ với một FRC?
Brent Priddy

5

Swift 3.0, UISearchControll, NSFetchedResultsControll và Core Data

Mã này sẽ hoạt động trên Swift 3.0 với Core Data! Bạn sẽ cần một phương thức ủy nhiệm duy nhất và một vài dòng mã để lọc và tìm kiếm các đối tượng từ mô hình. Sẽ không có gì cần thiết nếu bạn đã thực hiện tất cả FRCdelegatephương pháp của họ cũng nhưsearchController .

Phương UISearchResultsUpdatingthức giao thức

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

Đó là nó! Hy vọng nó sẽ giúp bạn! Cảm ơn


1

Chuyển đổi 3.0

Sử dụng textField, UISearchDisplayControll bị từ chối kể từ iOS 8, bạn sẽ phải sử dụng UISearchControll. Thay vì giao dịch với Trình điều khiển tìm kiếm, tại sao bạn không tạo cơ chế tìm kiếm của riêng mình? Bạn có thể tùy chỉnh nó nhiều hơn và có nhiều quyền kiểm soát hơn và không phải lo lắng về việc SearchContoder thay đổi và / hoặc không được dùng nữa.

Phương pháp này tôi sử dụng hoạt động rất tốt và không yêu cầu nhiều mã. Tuy nhiên, nó yêu cầu bạn sử dụng Dữ liệu lõi và triển khai NSFetchedResultsControll.

Đầu tiên, tạo một TextField và đăng ký nó với một phương thức:

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

Sau đó, tạo phương thức textFieldDidChange của bạn, được mô tả trong bộ chọn khi mục tiêu được thêm vào:

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

Sau đó, bạn muốn lọc danh sách trong filterList()phương thức bằng cách sử dụng biến vị ngữ NSPredicate hoặc NSCompound nếu nó phức tạp hơn. Trong phương thức filterList của tôi, tôi lọc dựa trên tên của thực thể và tên của đối tượng "subC loại" đối tượng (một mối quan hệ một đến nhiều).

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

Tôi nghĩ Luka có cách tiếp cận tốt hơn cho việc này. Xem LargeDataSetSamplelý do của anh ấy

Anh ấy không dùng FetchedResultsController , nhưng sử dụng bộ đệm khi tìm kiếm, do đó kết quả tìm kiếm xuất hiện nhanh hơn nhiều khi người dùng nhập nhiều hơn vào SearchBar

Tôi đã sử dụng phương pháp của anh ấy trong ứng dụng của mình và nó hoạt động tốt. Cũng nhớ rằng nếu bạn muốn làm việc với đối tượng Model, hãy làm cho nó đơn giản nhất có thể, xem câu trả lời của tôi về setProperIESToFetch


0

Đây là một cách xử lý fetchedResults với nhiều bộ dữ liệu vừa đơn giản vừa đủ chung để áp dụng hầu hết mọi nơi. Đơn giản chỉ cần lấy kết quả chính của bạn vào một mảng khi có một số điều kiện.

NSArray *results = [self.fetchedResultsController fetchedObjects];

Truy vấn mảng bằng cách lặp qua nó hoặc bất cứ điều gì bạn muốn để tạo một tập hợp con của FetchedResults chính của bạn. Và bây giờ bạn có thể sử dụng toàn bộ hoặc tập hợp con khi có một số điều kiện.


0

Tôi thực sự thích cách tiếp cận của @Josh O'Connor khi anh ấy không sử dụng a UISearchController. Bộ điều khiển này vẫn (Xcode 9) có lỗi bố cục mà nhiều người đang cố gắng khắc phục.

Tôi đã hoàn nguyên việc sử dụng UISearchBarthay vì a UITextField, và nó hoạt động khá độc đáo. Yêu cầu của tôi cho tìm kiếm / bộ lọc là để tạo ra một NSPredicate. Điều này được chuyển đến 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

Cuối cùng, kết nối SearchBar với đại biểu của nó.

Tôi hy vọng điều này sẽ giúp những người khác


0

Cách tiếp cận đơn giản để lọc UITableView hiện có bằng CoreData và đã được sắp xếp theo cách bạn muốn.

Điều này theo nghĩa đen quá tôi 5 phút để thiết lập và làm việc.

Tôi đã có sẵn UITableViewbằng cách sử dụng CoreDataDữ liệu từ iCloud và có tương tác người dùng khá phức tạp và tôi không muốn phải sao chép tất cả những thứ đó cho a UISearchViewController. Tôi chỉ có thể thêm một vị từ vào cái FetchRequestđã được sử dụng bởi FetchResultsControllervà bộ lọc dữ liệu đã được sắp xếp.

-(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];
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.