http://developer.apple.com/library/ios/samplecode/AdvancedTableSearch/Introduction/Intro.html
- このサンプルは UISearchDisplayController の使い方ですよ
- テーブル内部のデータでもfilteringできますよ
- 単純な文字列比較ではなくfilteredArrayUsingPredicateを使って比較をしていますよ
- その方法はNSPredicateを使うということですよ
- このサンプルではNSObjectのサブクラスの複数のフィールドの横断検索のためにNSCompoundPredicateとNSExpressionsを使っていますよ
- その方法はNSPredicateを使うということですよ
このサンプルではobjectは
- title
- year introduced
- price
の3つを持っていますよ
ユーザが"2007"と入れたら2007年発表のものが表示され、"2007 i"と入れたら2007年発表かつ"i"から始まるobjectが表示されますよ
まず前提としてUIViewControllerはsearchDisplayControllerをpropertyに持ってる UISearchDisplayDelegateプロトコルに準拠するため以下2つのメソッドを実装(293行目から)
- searchDisplayController:shouldReloadTableForSearchString:
- searchDisplayController:shouldReloadTableForSearchScope:
この中で
updateFilteredContentForSearchString
を呼び出していて、ここが検索のロジックになっている
- 横断検索の方法: NSPredicate(NSComparisonPredicate)を作ってArrayのfilteredArrayUsingPredicateに投げるだけ
self.searchResults = [self.products mutableCopy];
でテーブルの全データをコピーしていて、updateFilteredContentForSearchStringの最後でこのArrayのfilteredArrayUsingPredicateにNSPredicateを投げている
- NSPredicateについては以下のドキュメント
以下のコメントの通り、nameのkeyからcase-insensitive([c])でcontainsかどうか、year, priceのkeyからisEqualかどうかを検索するという実装
// each searchString creates an OR predicate for: name, yearIntroduced, introPrice
//
// example if searchItems contains "iphone 599 2007":
// name CONTAINS[c] "iphone"
// name CONTAINS[c] "599", yearIntroduced ==[c] 599, introPrice ==[c] 599
// name CONTAINS[c] "2007", yearIntroduced ==[c] 2007, introPrice ==[c] 2007
//
NSComparisonPredicateを使った比較の表現
NSPredicate *finalPredicate = [NSComparisonPredicate
predicateWithLeftExpression:lhs
rightExpression:rhs
modifier:NSDirectPredicateModifier
type:NSContainsPredicateOperatorType
options:NSCaseInsensitivePredicateOption];
- lhs(left hand side):左辺
- rhs(right hand side):右辺
- modifier:NSDirectPredicateModifier:直接比較(後述)
- type:NSContainsPredicateOperatorType:左辺が右辺を含んでいればtrue(後述)
- options:NSCaseInsensitivePredicateOption:case-insensitive
NSDirectPredicateModifierの他にALLとANYがある
// Describes how the operator is modified: can be direct, ALL, or ANY
typedef NS_ENUM(NSUInteger, NSComparisonPredicateModifier) {
NSDirectPredicateModifier = 0, // Do a direct comparison
NSAllPredicateModifier, // ALL toMany.x = y
NSAnyPredicateModifier // ANY toMany.x = y
};
NSContainsPredicateOperatorTypeの他に色々ある
// Type basic set of operators defined. Most are obvious; NSCustomSelectorPredicateOperatorType allows a developer to create an operator which uses the custom selector specified in the constructor to do the evaluation.
typedef NS_ENUM(NSUInteger, NSPredicateOperatorType) {
NSLessThanPredicateOperatorType = 0, // compare: returns NSOrderedAscending
NSLessThanOrEqualToPredicateOperatorType, // compare: returns NSOrderedAscending || NSOrderedSame
NSGreaterThanPredicateOperatorType, // compare: returns NSOrderedDescending
NSGreaterThanOrEqualToPredicateOperatorType, // compare: returns NSOrderedDescending || NSOrderedSame
NSEqualToPredicateOperatorType, // isEqual: returns true
NSNotEqualToPredicateOperatorType, // isEqual: returns false
NSMatchesPredicateOperatorType,
NSLikePredicateOperatorType,
NSBeginsWithPredicateOperatorType,
NSEndsWithPredicateOperatorType,
NSInPredicateOperatorType, // rhs contains lhs returns true
NSCustomSelectorPredicateOperatorType,
NSContainsPredicateOperatorType NS_ENUM_AVAILABLE(10_5, 3_0) = 99, // lhs contains rhs returns true
NSBetweenPredicateOperatorType NS_ENUM_AVAILABLE(10_5, 3_0)
};
yearIntroducedとpriceについても同様なものを作る 数字は完全一致の比較にしたいのでNSContainsPredicateOperatorTypeではなくNSEqualToPredicateOperatorTypeを使っている
最後にこれら3つのPredicateををor条件にする
// at this OR predicate to our master AND predicate
NSCompoundPredicate *orMatchPredicates = (NSCompoundPredicate *)[NSCompoundPredicate orPredicateWithSubpredicates:searchItemsPredicate];
finalCompoundPredicate =
(NSCompoundPredicate *)[NSPredicate predicateWithFormat:@"(SELF.type == %@)", type];
self.searchResults = [[self.searchResults filteredArrayUsingPredicate:finalCompoundPredicate] mutableCopy];
@interface NSArray (NSPredicateSupport)
- (NSArray *)filteredArrayUsingPredicate:(NSPredicate *)predicate; // evaluate a predicate against an array of objects and return a filtered array
@end