UISearchDisplayController / UISearchBar를 사용하여 NSFetchedResultsController (CoreData)를 필터링하는 방법


146

CoreData 기반 iPhone 앱에서 검색 코드를 구현하려고합니다. 진행 방법을 잘 모르겠습니다. 앱에는 이미 기본 TableView의 데이터를 검색하는 조건부와 함께 NSFetchedResultsController가 있습니다. 코드를 너무 많이 변경하기 전에 올바른 길을 가고 싶습니다. 많은 예제가 CoreData 대신 배열 기반이기 때문에 혼란 스럽습니다.

다음은 몇 가지 질문입니다.

  1. 일치하는 항목 만 검색하거나 기본 TableView와 동일한 항목을 사용할 수있는 두 번째 NSFetchedResultsController가 필요합니까?

  2. 동일한 것을 사용하는 경우 FRC 캐시를 지우고 handleSearchForTerm : searchString 메소드에서 술어를 변경하는 것이 간단합니까? 술어에는 검색어뿐만 아니라 초기 술어도 포함되어야합니까, 아니면 술어를 사용하여 처음부터 데이터를 검색했음을 기억합니까?

  3. 원래 결과로 돌아가려면 어떻게합니까? 검색 조건자를 nil로 설정합니까? FRC 결과를 검색하는 데 사용 된 원래 술어를 먼저 종료시키지 않습니까?

누구든지 FRC로 검색을 사용하는 코드 예제가 있다면 크게 감사하겠습니다!


@Brent, 완벽한 솔루션은 나를 위해 대접했습니다!
DetartrateD5

답변:


193

나는 실제로 내 프로젝트 중 하나에서 이것을 구현했습니다 (귀하의 질문과 다른 잘못된 대답은 수행 할 작업을 암시했습니다). Sergio의 답변을 시도했지만 실제로 장치에서 실행할 때 예외 문제가 발생했습니다.

예, 두 개의 페치 결과 컨트롤러를 만듭니다. 하나는 일반 디스플레이 용이고 다른 하나는 UISearchBar의 테이블 뷰용입니다.

하나의 FRC (NSFetchedResultsController) 만 사용하는 경우 원래 UITableView (검색 중 활성화 된 검색 테이블보기가 아님)는 검색하는 동안 콜백을 호출하고 필터링 된 버전의 FRC를 잘못 사용하려고 시도 할 수 있으며 예외가 표시됩니다. 섹션에서 잘못된 수의 섹션 또는 행에 대해 발생했습니다.

여기 내가 한 일이 있습니다 : fetchedResultsController 및 searchFetchedResultsController 속성으로 사용할 수있는 두 개의 FRC가 있습니다. 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

모든 UITableViewDelegate / DataSource 메소드로 작업 할 때 올바른 FRC를 검색하는 유용한 메소드를 작성했습니다.

- (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 :"메소드는 실제로 전체 프로세스를 단순화합니다.
다니엘 아미 테이

브렌트. 당신은 남자입니다. 그러나 여기 새로운 도전이 있습니다. 백그라운드 처리를 사용하여이 코드를 구현합니다. 내 응용 프로그램의 다른 부분에 대한 약간의 멀티 스레딩을 수행했지만 이것이 어렵습니다. 더 나은 사용자 경험을 제공 할 것이라고 생각합니다. 도전이 허용 되었습니까?
jschmidt

3
@BrentPriddy 감사합니다! 나는 당신의 코드를 리팩토링 수정 페치 요청 대신을 설정 searchFetchedResultsControllernil할 때마다 검색 텍스트 변경.
ma11hew28

2
귀하의 에서이 SO 질문 에서 지적한 것처럼 cellForRowAtIndexPath세포를 얻지 않아야 합니까? 이렇게하지 않으면 사용자 정의 셀이 표시되지 않습니다. self.tableView
amb December

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메소드 에서 NSPredicateif 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)로 조정하는 것이 좋습니다. 검색을 시작하고 검색 표시 줄에서 문자열을 삭제 한 후 테이블보기를 클릭하면 충돌이 발생하지 않습니다.
구토 아라 우호

17

이 작업을 수행하려면 몇 번의 시도가 필요했습니다 ...

이해의 열쇠는 여기에 두 개의 tableView가 있다는 것을 깨달았습니다. 하나는 내 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];
    }
}

그리고 controllerDidChangeContent :

- (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(모두의 이행과 관련된tableViewfetchedResultsController코드 와 것이 표준입니다) 하고 있습니다. 구현하기 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];
}

이 코드는 if-else 문 내부의 "표준" -fetchedResultsController BUT 과 매우 유사합니다 .

+andPredicateWithSubpredicates: -이 방법을 사용하여 주요 첫 번째 페치 결과를 저장할 술어를 설정할 수 있습니다. tableView

+orPredicateWithSubpredicates -이 방법을 사용하여 기존 검색어를 검색어로 필터링합니다. searchBar

결국 나는이 특정 페치에 대한 복합 술어로 술어 배열을 설정하고 있습니다. 필수 술어의 경우 AND 또는 선택적의 경우.

그리고 그게 전부입니다! 더 이상 구현할 필요가 없습니다. 행복한 코딩!


5

실시간 검색을 사용하고 있습니까?

그렇지 않은 경우 사용자가 "검색"을 누를 때 이전에 사용한 검색이 포함 된 배열 (또는 NSFetchedResultsController)을 원할 경우 FetchedResults에 술어를 변경하도록 지시합니다.

어느 쪽이든, 매번 FetchedResults를 다시 작성해야합니다. NSFetchedResultsController를 하나만 사용하는 것이 좋습니다. 코드를 많이 복제해야하고 표시하지 않는 무언가에 메모리를 낭비 할 필요가 없기 때문입니다.

NSString "searchParameters"변수가 있고 FetchedResults 메소드가 필요한 경우 검색 매개 변수를 사용하여 필요에 따라 해당 변수를 다시 빌드하는지 확인하십시오.

a) "searchParameters"를 무언가로 설정하십시오 (또는 모든 결과를 원한다면 nil).

b) 현재 NSFetchedResultsController 객체를 해제하고 nil로 설정합니다.

c) 테이블 데이터를 다시로드하십시오.

간단한 코드는 다음과 같습니다.

- (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 델리게이트 메소드는 검색 할 때 메모리가 적은 장치에서 검색 할 때 메인 테이블보기가 셀을 다시로드하려고합니다.
브렌트 프리디

누구든지 이것에 대한 프로젝트 템플릿에 대한 링크가 있습니까? 나는 어디로 가는지 알아내는 것이 정말로 어렵다는 것을 알게되었습니다. 작동하는 템플릿을 참조로 사용하는 것이 좋습니다.
RyeMAC3

@ 브렌트, FRC 델리게이트 메소드에서 변경이 필요한 검색 테이블 뷰인지 확인해야합니다 .FRC 및 UITableView 델리게이트 메소드에서 올바른 테이블을 업데이트하고 업데이트하면 기본 테이블 뷰 및 테이블 뷰를 검색하십시오.
kervich

@kervich 나는 당신이 위의 대답을 묘사하고 있다고 생각합니까, 아니면 하나의 FRC로 할 수 있다고 말하고 있습니까?
브렌트 프리디

5

Swift 3.0, UISearchController, NSFetchedResultsController 및 핵심 데이터

이 코드는 Swift 3.0에서 작동합니다 Core Data! 모델에서 객체를 필터링하고 검색하려면 단일 델리게이트 메소드와 몇 줄의 코드가 필요합니다. FRCdelegate메소드를 모두 구현 한 경우 아무것도 필요하지 않습니다 searchController.

UISearchResultsUpdating프로토콜 방법

func updateSearchResults(for searchController: UISearchController) {

    let text = searchController.searchBar.text

    if (text?.isEmpty)! {
       // Do something 
    } else {
        self.fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "( someString contains[cd] %@ )", text!)
    }
    do {
        try self.fetchedResultsController.performFetch()
        self.tableView.reloadData()
    } catch {}
}

그게 다야! 그것이 당신을 돕는 희망! 감사


1

스위프트 3.0

textField를 사용하십시오. UISearchDisplayController는 iOS 8부터 사용되지 않으므로 UISearchController를 사용해야합니다. 검색 컨트롤러를 다루는 대신 자신 만의 검색 메커니즘을 만드십시오. 더 많이 사용자 정의하고 더 많은 제어권을 가질 수 있으며 SearchController 변경 및 / 또는 더 이상 사용되지 않을 것에 대해 걱정할 필요가 없습니다.

내가 사용하는이 방법은 매우 잘 작동하며 많은 코드가 필요하지 않습니다. 그러나 Core Data를 사용하고 NSFetchedResultsController를 구현해야합니다.

먼저 TextField를 만들고 메소드로 등록하십시오.

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

그런 다음 대상이 추가 될 때 선택기에 설명 된 textFieldDidChange 메소드를 작성하십시오.

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

그런 다음 filterList()NSPredicate 또는 NSCompound 조건자를 사용하여 메소드 에서 목록을 필터링하면 더 복잡합니다. filterList 메서드에서 엔터티 이름과 엔터티 "sub 카테고리"개체 이름 (일대 다 관계)을 기준으로 필터링합니다.

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가 더 나은 접근 방식을 가지고 있다고 생각합니다. LargeDataSetSample그의 이유를 참조하십시오.

그는 사용하지 않습니다 FetchedResultsController 검색 할 때 캐시를 사용하므로 사용자가 SearchBar에 더 많이 입력하면 검색 결과가 훨씬 빠르게 나타납니다.

내 앱에서 그의 접근 방식을 사용했으며 정상적으로 작동합니다. 또한 Model 객체로 작업하고 싶다면 가능한 한 간단하게 만드십시오. setPropertiesToFetch 에 대한 내 대답을 참조하십시오


0

다음은 거의 모든 곳에 적용 할 수있을 정도로 단순하고 일반적인 여러 데이터 세트로 fetchedResults를 처리하는 방법입니다. 어떤 조건이 존재할 때 주요 결과를 배열로 가져 가면됩니다.

NSArray *results = [self.fetchedResultsController fetchedObjects];

기본 fetchedResults의 하위 집합을 만들기 위해 배열이나 원하는 것을 반복하여 배열을 쿼리하십시오. 이제 일부 조건이 존재할 때 전체 세트 또는 서브 세트를 사용할 수 있습니다.


0

나는 그가 사용하지 않는 @Josh O'Connor의 접근 방식을 정말로 좋아했습니다. UISearchController . 이 컨트롤러에는 여전히 (Xcode 9) 많은 사람들이 해결하려는 레이아웃 버그가 있습니다.

나는 UISearchBar대신 을 사용하여 되 돌렸고 UITextField꽤 잘 작동합니다. 검색 / 필터에 대한 요구 사항은 NSPredicate. 이것은 FRC로 전달됩니다.

   class ViewController: UIViewController, UITableViewDelegate, 
 UITableViewDataSource, UISearchBarDelegate {

        @IBOutlet var searchBar: UISearchBar!

         func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

                shouldShowSearchResults = true

                if let queryString = searchBar.text {
                    filterList(queryString)

                    fetchData()
                }
            }



          func filterList(_ queryString: String) {
        if (queryString == "") {
            searchPredicate = nil
    }
        else {
            let brandPredicate = NSPredicate(format: "brand CONTAINS[c] %@", queryString)
            let modelPredicate = NSPredicate(format: "model CONTAINS[c] %@", queryString)
            let orPredicate = NSCompoundPredicate(type: .or, subpredicates: [brandPredicate, modelPredicate])
            searchPredicate = orPredicate
    }

}

...

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        let request = NSFetchRequest<NSFetchRequestResult>(entityName: filmEntity)
        request.returnsDistinctResults = true
        request.propertiesToFetch = ["brand", "model"]

        request.sortDescriptors = [sortDescriptor]
        request.predicate = searchPredicate

마지막으로 SearchBar를 대리인에게 연결하십시오.

나는 이것이 다른 사람들을 돕기를 바랍니다


0

CoreData를 사용하여 기존 UITableView를 필터링하는 간단한 접근 방식으로 이미 원하는 방식으로 정렬되었습니다.

이것은 문자 그대로 너무 5 분 설정하고 작동합니다.

iCloud의 Data로 채워진 기존 UITableView사용 이 있었고 CoreData사용자 상호 작용이 매우 복잡하여에 대한 모든 것을 복제하고 싶지 않았습니다 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.