web-dev-qa-db-ja.com

管理対象オブジェクトのプロパティを変更しても、NSFetchedResultsControllerがテーブルビューを更新するトリガーにはなりません

fetchedResultsController述語があります。ここで"isOpen == YES"

closeCurrentClockSetを呼び出すときは、そのプロパティを[〜#〜] no [〜#〜]に設定します。したがって、tableViewに表示されなくなります。

いくつかの理由で、これは起こっていません。

誰かが私がこれを理解するのを手伝ってもらえますか?

-(void)closeCurrentClockSet
{

    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == YES"];

    NSArray *fetchedObjects =
        [self fetchRequestForEntity:@"ClockSet"
                      withPredicate:predicate
             inManagedObjectContext:[myAppDelegate managedObjectContext]];

    ClockSet *currentClockSet = (ClockSet *)fetchedObjects.lastObject;

    [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];

}

-

カスタムfetchRequestForEntity:withPredicate:inManagedObjectContextメソッドを呼び出すことにより、まったく同じアプローチを使用して、さらにいくつかのメソッドがあります。

これらのメソッドでは、プロパティを変更すると、tableViewが正しく更新されます。しかし、上記のこれ(closeCurrentClockSet)はそうではありません!理由がわかりません。

-

私のfetchedResultsControllerの実装は、Appleのドキュメントからのものです。

また、別の詳細。アプリをバックグラウンドに送信した場合。アプリを閉じて再度開くと、tableViewが更新されて表示されます!

私はここstackOverflowで以前の質問に従うために最善を尽くしました。運がない。私もこれを骨にNSLoggedしました。オブジェクトは正しくフェッチされています。それは正しいものです。 isOpen Propertyは正しく更新されています[〜#〜] no [〜#〜]。しかし、何らかの理由で、fetchedResultsControllerがtableViewを更新しません。

私は、reloadDataやperformFetchの呼び出しなど、いくつかの「ハンマー」ソリューションを試しました。しかし、それはうまくいきませんでした。またはそれらを使用するのは理にかなっています...

編集:スクラッチ、それDID動作、resultsControllerでperformFetchの直後にreloadDataを呼び出すしかし、reloadDataを使用すると、解決策が得られます。さらに、すべてのアニメーションが削除されます。IコントローラにtableViewを自動更新させたい。

誰かが私がこれを理解するのを手伝ってくれる?

どんな助けでも大歓迎です!

ありがとうございました、

ヌーノ

編集:

完全な実装。

fetchedResultsControllerは、かなり標準的で簡単です。他のすべてはAppleのドキュメントからのものです

- (NSFetchedResultsController *)fetchedResultsController
{

    if (_fetchedResultsController) {
        return _fetchedResultsController;
    }

    NSManagedObjectContext * managedObjectContext = [myAppDelegate managedObjectContext];

    NSEntityDescription *entity  =
        [NSEntityDescription entityForName:@"ClockPair"
                    inManagedObjectContext:managedObjectContext];

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        [fetchRequest setEntity:entity];

    NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];
        [fetchRequest setPredicate: [NSPredicate predicateWithFormat:predicate]];

    NSSortDescriptor *sortDescriptor1 =
        [[NSSortDescriptor alloc] initWithKey:@"clockIn" ascending:NO];

    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];

        [fetchRequest setSortDescriptors:sortDescriptors];
        [fetchRequest setFetchBatchSize:20];

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


    _fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;

    return _fetchedResultsController;

}

-

Appleのドキュメントからのボイラープレートコード:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    [self.tableView beginUpdates];
}



- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            break;

        case NSFetchedResultsChangeDelete:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeUpdate:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeMove:

            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationLeft];

            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            break;
    }
}



- (void)controller:(NSFetchedResultsController *)controller
  didChangeSection:(id )sectionInfo
           atIndex:(NSUInteger)sectionIndex
     forChangeType:(NSFetchedResultsChangeType)type
{

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:

            [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                     withRowAnimation:UITableViewRowAnimationFade];

            break;

        case NSFetchedResultsChangeDelete:

            [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                     withRowAnimation:UITableViewRowAnimationFade];

            break;
    }
}



- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.tableView endUpdates];
}

最初の更新:

追跡[managedObjectContext hasChanges]は、必要に応じてYESを返します。ただし、fetchedResultsControllerはtableViewを更新しません

2回目の更新

didChangeObject:atIndexPath:呼び出されませんこの特定のケースでは!私にはさらに2つのメソッドがあり、まったく同じコードで、たまたま別のエンティティになっています。そして、それらは完全に機能します。これを指摘してくれてありがとう@Leonardo

TH UPDATEこのメソッドは、同じルールに従います。しかし、実際には機能します。

- (void)clockOut
{
    NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == %@", [NSNumber numberWithBool:YES]];

    NSArray * fetchedObjects =
        [self fetchRequestForEntity:@"ClockPair"
                      withPredicate:predicate
             inManagedObjectContext:[myAppDelegate managedObjectContext]];

    ClockPair *aClockPair = (ClockPair *)fetchedObjects.lastObject;

    aClockPair.clockOut = [NSDate date];
    aClockPair.isOpen   = [NSNumber numberWithBool:NO];


}

誰かが私が欠けているかもしれないものについて他のアイデアを持っていますか?

ありがとうございました、

ヌーノ

25
nmdias

OK、問題を説明してから、FRCのバグかどうかを判断させていただきます。バグだと思われる場合は、Appleにバグレポートを提出する必要があります。

フェッチ結果コントローラーの述語は次のようになります。

NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];

これはブール値の有効な述語です。 clockSetエンティティの関係に従い、そのisOpen属性を取得します。 YESの場合、それらのオブジェクトはオブジェクトの配列に受け入れられます。

ここまではいいと思います。

ここで、clockSet.isOpen属性の1つをNOに変更すると、そのオブジェクトがテーブルビューから消えることが期待されます(つまり、述語と一致しなくなったため、オブジェクトから削除する必要があります。フェッチされたオブジェクトの配列)。

だから、あなたがこれを持っているなら...

[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];

次に、currentClockSetと関係のある最上位オブジェクトは、フェッチされた結果のFRC配列から「消える」はずです。

しかし、あなたはそれが消えるのを見ません。その理由は、FRCによって監視されているオブジェクトが変更されていないためです。はい、述語キーパスが変更されましたが、FRCはClockPairのエンティティを保持し、ClockSetエンティティは実際に変更されました。

通知が飛び交うのを見て、舞台裏で何が起こっているかを確認できます。

とにかく、FRCはフェッチを実行するときにキーパスを使用しますが、フェッチされたオブジェクトの実際のセットにないオブジェクトへの変更を監視しません。

最も簡単な回避策は、このキーパスオブジェクトを保持するオブジェクトの属性を「設定」することです。

たとえば、ClockPairにもisOpen属性があることに気づきました。あなたが逆の関係を持っているなら、あなたはこれをすることができます...

currentClockSet.isOpen = NO;
currentClockSet.clockPair.isOpen = currentClockSet.clockPair.isOpen;

実際には値をまったく変更していないことに注意してください。ただし、セッターが呼び出され、KVOがトリガーされたため、プライベートDidChange通知が呼び出され、オブジェクトが変更されたことをFRCに通知しました。したがって、チェックを再評価して、オブジェクトを含める必要があるかどうかを確認し、変更されたキーパス値を見つけて、期待どおりに実行します。

したがって、FRC述語でキーパスを使用する場合、その値を変更すると、FRC配列内のすべてのオブジェクトに戻って「ダーティアップ」し、それらのオブジェクトが通知に含まれるようにする必要があります。オブジェクトの変更について渡されました。醜いですが、フェッチ要求を保存または変更して再フェッチするよりもおそらく優れています。

あなたが私を信じていないことを知っているので、先に進んで試してみてください。それが機能するためには、オブジェクトのFRC配列内のどのアイテムが変更の影響を受けるかを知り、FRCに変更を認識させるためにそれらを「突く」必要があることに注意してください。

もう1つのオプションは、前述したように、コンテキストを保存して値を再フェッチすることです。コンテキストを保存したくない場合は、ストアから更新せずに、フェッチに現在のコンテキストの更新を含めることができます。

FRCが監視しているオブジェクトへの変更を偽造することが、他のエンティティへの重要なパスである述語の再評価を達成するための最良の方法であることがわかりました。

さて、これがバグであるかどうかは議論の余地があります。個人的には、FRCがキーパスを監視する場合は、ここで見られるように部分的にではなく、ずっと監視する必要があると思います。

それが理にかなっていることを願っています。バグレポートを提出することをお勧めします。

74
Jody Hagins

同様の問題が発生しました。

私はこの質問がかなり古いことを知っていますが、これが他の誰かに役立つことを願っています:

最も簡単な方法は、親オブジェクトに_lastUpdated: NSDate_という名前の新しいプロパティを導入することでした。

いくつかのConversationを含むMessagesがありました。メッセージのisReadフラグが更新されるたびに、ConversationOverviewViewControllersのみを表示するConversationの更新が必要でした。さらに、NSFetchedResultsControllerConversationOverviewVCConversationsのみをフェッチし、Messageについては何も知りません。

メッセージが更新されるたびに、私はmessage.parentConversation.lastUpdated = NSDate()を呼び出しました。これは、更新を手動でトリガーする簡単で便利な方法です。

お役に立てれば。

3
Chris

[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];管理対象オブジェクトのコンテキストを保存できますか?

NSError *saveError = nil;
if( ![[myAppDelegate managedObjectContext] save:&saveError] ) {
    // handle error saving context
}

コンテキストを保存した後、UITableViewが正しく更新されると思います。これが、アプリをバックグラウンドに送信することが機能する理由である可能性があります。 Core Dataスタックは、バックグラウンドに移行したときにメインのNSManagedObjectContextで保存を実行するようにアプリケーションのデリゲートで設定されていると思われます。

0
FluffulousChimp