iPhone連絡先アプリスタイルのインデックス付きテーブルビューの実装
質問
私の要件:インデックスのテーブルビューでアルファベット順に人の名前をリストするというこの簡単な要件があります。インデックスタイトルはアルファベットの開始文字です(さらに、上部の検索アイコンと#が数字やその他の特別から始まるその他の値を表示する文字)。
私がこれまでにやったこと:1.ストレージにコアデータを使用しており、「last_name」は連絡先エンティティの文字列プロパティとしてモデル化されています。
私の要件を達成する問題:1.最初に、セクションインデックスタイトルをアルファベットの最初の文字にすることができませんでした。次の投稿でのデイブの提案は、私が同じことを達成するのに役立ちました。 文字列の最初の文字によって作成されたセクションを持つnsfetchedResultScontroller
Daveの提案で私が遭遇した唯一の問題は、「#」インデックスの下でグループ化されたMISCの名前を取得できなかったということです。
私が試したこと:1. NSString(カテゴリ)にカスタム比較メソッドを追加して、比較とセクションの作成方法を確認しましたが、NSSORTDESCRIPTORセレクターで指定されたときにそのカスタムメソッドは呼び出されません。
ここにいくつかのコードがあります:
@interface NSString (SortString)
-(NSComparisonResult) customCompare: (NSString*) aStirng;
@end
@implementation NSString (SortString)
-(NSComparisonResult) customCompare:(NSString *)aString
{
NSLog(@"Custom compare called to compare : %@ and %@",self,aString);
return [self caseInsensitiveCompare:aString];
}
@end
データを取得するコード:
NSArray *sortDescriptors = [NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:@"last_name"
ascending:YES selector:@selector(customCompare:)] autorelease]];
[fetchRequest setSortDescriptors:sortDescriptors];
fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:@"lastNameInitial" cacheName:@"MyCache"];
私が欠けているものと要件をどのように達成できるかを教えてもらえますか?
解決
これは、この問題の非常に非効率的なファーストパスであり、最終的に書き直します。しかし、うまくいけば、これがあなたに役立つでしょう。
これの考え方は、「標準」セクションインデックスビューをタップするときに、実際のテーブルセクションインデックスを「保証」することです。標準セクションインデックスビューには、検索用の拡大レンズアイコン、非アルファベット科目セクションのハッシュマーク(#)、アルファベット科目セクションの文字AからZが必要です。
この標準ビューは、実際のセクションがいくつあるか、何で作られているかに関係なく提示されます。
最終的に、このコードマップセクションインデックスは、フェッチされた結果コントローラーの実質存在するアルファベットセクション名パス、または実現する非アルファベット(数値)セクション、またはテーブルヘッダーの検索フィールドに表示されます。
ユーザーは、セクションインデックスマッピングアレイをたまに再作成するだけです(_idxArray
)セクションインデックスの各タッチでは、各タッチで配列を再作成することは明らかに非効率的であり、事前に計算された結果をキャッシュするために微調整することができます。
これを締め始める場所がたくさんあります:私は作ることができます sectionIndexTitleLetters
たとえば、static stringは最初からすべて大文字です。ただし、3GSの電話では十分に速いので、最近これを再訪していません。
ヘッダー:
static NSString *sectionIndexTitleLetters = @"abcdefghijklmnopqrstuvwxyz";
テーブルの実装では、データソースを表示します。
- (NSArray *) sectionIndexTitlesForTableView:(UITableView *)tv {
if (tv != searchDisplayController.searchResultsTableView) {
NSMutableArray *_indexArray = [NSMutableArray arrayWithCapacity:([sectionIndexTitleLetters length]+2)];
[_indexArray addObject:@"{search}"];
[_indexArray addObject:@"#"];
for (unsigned int _charIdx = 0; _charIdx < [sectionIndexTitleLetters length]; _charIdx++) {
char _indexChar[2] = { toupper([sectionIndexTitleLetters characterAtIndex:_charIdx]), '\0'};
[_indexArray addObject:[NSString stringWithCString:_indexChar encoding:NSUTF8StringEncoding]];
}
return _indexArray;
}
return nil;
}
- (NSInteger) tableView:(UITableView *)tv sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
if (tv != searchDisplayController.searchResultsTableView) {
if (index == 0) {
//
// This is the search bar "section"
//
[currentTableView scrollRectToVisible:[[currentTableView tableHeaderView] bounds] animated:YES];
return -1;
}
else if (index == 1) {
//
// This is the "#" section, which covers non-alphabetic section headers (e.g. digits 0-9)
//
return 0;
}
else {
//
// This is a bit more involved because the section index array may contain indices that do not exist in the
// fetched results controller's sections->name info.
//
// What we are doing here is building a "fake-index" array that will return a real section index regardless of
// whether the section index title being touched exists or not.
//
// The fake array will be of length of the section index title array, and each index will contain an unsigned
// integer from 1 to {numOfRealSections}.
//
// The value this array returns will be "nearest" to the real section that is in the fetched results controller.
//
NSUInteger _alphabeticIndex = index-2;
unsigned int _idxArray[26];
for (unsigned int _initIdx = 0; _initIdx < [sectionIndexTitleLetters length]; _initIdx++) {
_idxArray[_initIdx] = [[fetchedResultsController sections] count] - 1;
}
unsigned int _previousChunkIdx = 0;
NSNumberFormatter *_numberFormatter = [[NSNumberFormatter alloc] init];
NSLocale *_enUSLocale = [[NSLocale alloc] initWithLocaleIdentifier: @"en_US"];
[_numberFormatter setLocale:_enUSLocale];
[_enUSLocale release];
for (unsigned int _sectionIdx = 0; _sectionIdx < [[fetchedResultsController sections] count]; _sectionIdx++) {
NSString *_sectionTitle = [[[fetchedResultsController sections] objectAtIndex:_sectionIdx] name];
if (![_numberFormatter numberFromString:_sectionTitle]) {
// what's the index of the _sectionTitle across sectionIndexTitleLetters?
for (unsigned int _titleCharIdx = 0; _titleCharIdx < [sectionIndexTitleLetters length]; _titleCharIdx++) {
NSString *_titleCharStr = [[sectionIndexTitleLetters substringWithRange:NSMakeRange(_titleCharIdx, 1)] uppercaseString];
if ([_titleCharStr isEqualToString:_sectionTitle]) {
// put a chunk of _sectionIdx into _idxArray
unsigned int _currentChunkIdx;
for (_currentChunkIdx = _previousChunkIdx; _currentChunkIdx < _titleCharIdx; _currentChunkIdx++) {
_idxArray[_currentChunkIdx] = _sectionIdx - 1;
}
_previousChunkIdx = _currentChunkIdx;
break;
}
}
}
}
[_numberFormatter release];
return (NSInteger)_idxArray[_alphabeticIndex];
}
}
return 0;
}
他のヒント
私は素朴かもしれませんが、なぜこれらのソリューションがそんなにバロックであるのかわかりません。これは私がしました:
私のモデルでは、方法を追加しました。
-(NSString *)lastInitial {
return [self.lastname substringToIndex:1];
}
そして、私のTablecontrollerでは、その方法を使用するようにfetchedResultScontrollerを設定しました。
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"lastInitial" cacheName:@"Master"];
うまくいくようです - これが悪い考えである理由はありますか?それとも、iOS5などの新機能の恩恵を受けていますか?