web-dev-qa-db-ja.com

文字列の最初の文字で作成されたセクションを持つNSFetchedResultsController

IPhoneでコアデータを学習します。テーブルビューにセクションを表示するコアデータの例はほとんどないようです。 CoreDataBooks の例ではセクションを使用していますが、モデル内の完全な文字列から生成されます。 Core Dataテーブルを、姓の最初の文字であるアドレス帳に基づいてセクションに整理したいと思います。

セクション分割として機能するために、各人に別の属性、つまり単一の文字を作成して作成することもできますが、これは気味悪いようです。

ここに私が始めているものがあります...トリックはsectionNameKeyPathをだましているようです:

- (NSFetchedResultsController *)fetchedResultsController {
//.........SOME STUFF DELETED
    // Edit the sort key as appropriate.
    NSSortDescriptor *orderDescriptor = [[NSSortDescriptor alloc] initWithKey:@"personName" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:orderDescriptor, nil];

    [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:managedObjectContext 
            sectionNameKeyPath:@"personName" cacheName:@"Root"];
//....
}
70
Greg Combs

NSStringのカテゴリを使用する別のオプションがあると思います...

@implementation NSString (FetchedGroupByString)
- (NSString *)stringGroupByFirstInitial {
    if (!self.length || self.length == 1)
        return self;
    return [self substringToIndex:1];
}
@end

FRCの作成中に、少し後で:

- (NSFetchedResultsController *)newFRC {
    NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:awesomeRequest
            managedObjectContext:coolManagedObjectContext
            sectionNameKeyPath:@"lastName.stringGroupByFirstInitial"
            cacheName:@"CoolCat"];
    return frc;
}

これが今、私のお気に入りのアプローチです。実装が非常にクリーンで簡単です。さらに、サポートするためにオブジェクトモデルクラスに変更を加える必要はありません。これは、セクション名がNSStringに基づくプロパティを指している限り、任意のオブジェクトモデルで機能することを意味します

54
Greg Combs

Dave DeLongのアプローチは、少なくとも私の場合は、いくつかのことを省略している限り、優れています。これが私にとってどのように機能するかです:

  • 「lastNameInitial」と呼ばれるエンティティに新しいオプションの文字列属性を追加します(またはその効果があります)。

    このプロパティを一時的にします。つまり、Core Dataはそれをデータファイルに保存しません。このプロパティは、必要なときにのみメモリに存在します。

    このエンティティのクラスファイルを生成します。

    このプロパティのセッターについて心配する必要はありません。このゲッターを作成します(これは魔法の半分です、私見)


// THIS ATTRIBUTE GETTER GOES IN YOUR OBJECT MODEL
- (NSString *) committeeNameInitial {
    [self willAccessValueForKey:@"committeeNameInitial"];
    NSString * initial = [[self committeeName] substringToIndex:1];
    [self didAccessValueForKey:@"committeeNameInitial"];
    return initial;
}


// THIS GOES IN YOUR fetchedResultsController: METHOD
// Edit the sort key as appropriate.
NSSortDescriptor *nameInitialSortOrder = [[NSSortDescriptor alloc] 
        initWithKey:@"committeeName" ascending:YES];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]];

NSFetchedResultsController *aFetchedResultsController = 
        [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
        managedObjectContext:managedObjectContext 
        sectionNameKeyPath:@"committeeNameInitial" cacheName:@"Root"];

前:デイブのレターへの最初のステップに従って、無効な引数例外を使用してsetPropertiesToFetchで終了するという問題が発生しました。以下のコードとデバッグ情報を記録しました。

NSDictionary * entityProperties = [entity propertiesByName];
NSPropertyDescription * nameInitialProperty = [entityProperties objectForKey:@"committeeNameInitial"];
NSArray * tempPropertyArray = [NSArray arrayWithObject:nameInitialProperty];

//  NSARRAY * tempPropertyArray RETURNS:
//    <CFArray 0xf54090 [0x30307a00]>{type = immutable, count = 1, values = (
//    0 : (<NSAttributeDescription: 0xf2df80>), 
//    name committeeNameInitial, isOptional 1, isTransient 1,
//    entity CommitteeObj, renamingIdentifier committeeNameInitial, 
//    validation predicates (), warnings (), versionHashModifier (null), 
//    attributeType 700 , attributeValueClassName NSString, defaultValue (null)
//    )}

//  NSInvalidArgumentException AT THIS LINE vvvv
[fetchRequest setPropertiesToFetch:tempPropertyArray];

//  *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
//    reason: 'Invalid property (<NSAttributeDescription: 0xf2dfb0>), 
//    name committeeNameInitial, isOptional 1, isTransient 1, entity CommitteeObj, 
//    renamingIdentifier committeeNameInitial, 
//    validation predicates (), warnings (), 
//    versionHashModifier (null), 
//    attributeType 700 , attributeValueClassName NSString, 
//    defaultValue (null) passed to setPropertiesToFetch: (property is transient)'

[fetchRequest setReturnsDistinctResults:YES];

NSSortDescriptor * nameInitialSortOrder = [[[NSSortDescriptor alloc]
    initWithKey:@"committeeNameInitial" ascending:YES] autorelease];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]];

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] 
    initWithFetchRequest:fetchRequest 
    managedObjectContext:managedObjectContext 
    sectionNameKeyPath:@"committeeNameInitial" cacheName:@"Root"];
62
Greg Combs

might動作させる方法は次のとおりです。

  • 「lastNameInitial」と呼ばれるエンティティに新しいオプションの文字列属性を追加します(またはその効果があります)。
  • このプロパティを一時的にします。つまり、Core Dataはそれをデータファイルに保存しません。このプロパティは、必要なときにのみメモリに存在します。
  • このエンティティのクラスファイルを生成します。
  • このプロパティのセッターについて心配する必要はありません。このゲッターを作成します(これは魔法の半分です、私見)

    -(NSString *)lastNameInitial {
    [self willAccessValueForKey:@ "lastNameInitial"];
    NSString * initial = [[self lastName] substringToIndex:1];
    [self didAccessValueForKey:@ "lastNameInitial"];
    イニシャルを返します。
    }
  • フェッチリクエストでは、次のように、このPropertyDescriptionのみをリクエストします(これは魔法の4分の1です)。

    NSDictionary * entityProperties = [myEntityDescription propertiesByName];
    NSPropertyDescription * lastNameInitialProperty = [entityProperties objectForKey:@ "lastNameInitial"];
    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:lastNameInitialProperty]];
  • フェッチリクエストが明確な結果のみを返すようにしてください(これは魔法の最後の4分の1です)。

    [fetchRequest setReturnsDistinctResults:YES];
  • この手紙で結果を注文します。

    NSSortDescriptor * lastNameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey:@ "lastNameInitial" ascending:YES] autorelease];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:lastNameInitialSortOrder]];
  • リクエストを実行し、それがあなたに与えるものを見てください。

これがどのように機能するかを理解すれば、NSManagedObjectsの配列を返すと推測します。それぞれの配列には、lastNameInitialプロパティのみがメモリにロードされ、誰が個別の姓のイニシャルのセットですか。

幸運を祈ります。これがどのように機能するかについて報告してください。私はこれを私の頭の上で作り上げ、これがうまくいくかどうか知りたいです。 =)

15
Dave DeLong

上記のGreg Combsの回答が好きです。文字列を大文字に変換することで、「Smith」や「smith」などの文字列が同じセクションに表示されるように、わずかな変更を加えました。

- (NSString *)stringGroupByFirstInitial {
    NSString *temp = [self uppercaseString];
    if (!temp.length || temp.length == 1)
        return self;
    return [temp substringToIndex:1];
}
8
fawsha1

私は常にこの問題に遭遇します。私がいつも戻ってくると思われる最良の解決策は、エンティティに実際の最初の初期プロパティを与えることです。実際のフィールドであると、フィールドをインデックス付きに設定できるため、検索と順序付けがより効率的になります。データが最初にインポート/作成されたときに、最初のイニシャルを引き出して2番目のフィールドにデータを入力するのは、あまり手間がかかるようには思えません。どちらの方法でも、最初の解析コードを記述する必要がありますが、エンティティごとに一度だけ実行し、二度と実行することはできません。欠点は、エンティティ(およびインデックス)ごとに1つの余分な文字を実際に格納しているように思われますが、それはおそらく重要ではありません。

補足説明。生成されたエンティティコードを変更することは避けます。たぶん私は何かが欠けていますが、CoreDataエンティティを生成するためのツールはそこに入れたかもしれないコードを尊重しません。コードを生成するときに選択したいずれかのオプションは、私が行ったカスタマイズを削除します。エンティティを巧妙な小さな関数で埋めると、そのエンティティに一連のプロパティを追加する必要があり、簡単に再生成できません。

6
Mike Baldus

スイフト3

まず、NSStringの拡張機能を作成します(CoreDataは基本的にNSStringを使用しているため)

extension NSString{
    func firstChar() -> String{
        if self.length == 0{
            return ""
        }
        return self.substring(to: 1)
    }
}

次に、firstCharキーパス、私の場合はlastname.firstCharを使用してソートします

request.sortDescriptors = [
            NSSortDescriptor(key: "lastname.firstChar", ascending: true),
            NSSortDescriptor(key: "lastname", ascending: true),
            NSSortDescriptor(key: "firstname", ascending: true)
        ]

最後に、sectionNameKeyPathにfirstCharキーパスを使用します

let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")
2