26

我有一个带有谓词的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 属性正在正确更新为NO。但由于某种原因,我的 fetchedResultsController 没有更新 tableView。

我确实尝试了几个“锤子”解决方案,比如 reloadData 和调用 performFetch。但这没有用。或者使用它们会有意义......

编辑:从头开始,它确实有效,在我的 resultsController 上的 performFetch 之后立即调用 reloadData但使用 reloadData 正在敲定一个解决方案。另外,它会删除所有动画。我希望我的控制器自动更新我的 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

第二次更新

didChangeObject:atIndexPath:在这种特殊情况下不会被调用!我还有 2 种方法,使用完全相同的代码,它们只是碰巧是不同的实体。他们完美地工作。感谢@Leonardo 指出这一点

第三次更新此方法,遵循相同的规则。但确实有效。

- (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];


}

有人对我可能遗漏的内容有任何其他想法吗?

谢谢,

努诺

4

3 回答 3

79

好的,我将解释您的问题,然后我将让您判断是否是FRC中的错误。如果您认为这是一个错误,那么您真的应该向苹果提交错误报告。

您的获取结果控制器谓词是这样的:

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

这是布尔值的有效谓词。它将遵循clockSet实体的关系并获取其isOpen属性。如果是,YES那么这些对象将被接受到对象数组中。

我认为我们在这里做得很好。

现在,如果您将clockSet.isOpen属性之一更改为NO,那么您希望看到该对象从您的表视图中消失(即,它不应再与谓词匹配,因此应将其从获取的对象数组中删除)。

所以,如果你有这个...

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

然后,无论哪个顶级对象都currentClockSet应该从您的 FRC 获取结果数组中“消失”。

但是,您不会看到它消失。原因是FRC监控的对象没有改变。是的,谓词键路径发生了变化,但 FRC 持有ClockPair的实体和ClockSet实际发生了变化的实体。

您可以观看通知飞来飞去,了解幕后发生的事情。

无论如何,当您进行提取时,FRC 将使用一个键路径,但它不会监视对不在其实际提取对象集中的对象的更改。

最简单的解决方法是为包含此关键路径对象的对象“设置”一个属性。

例如,我注意到ClockPair也有一个isOpen属性。如果你有一个反向关系,那么你可以这样做......

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

请注意,您实际上根本没有更改该值。然而,setter 被调用,触发了 KVO,因此触发了私有 DidChange 通知,然后通知 FRC 对象发生了变化。因此,它重新评估检查以查看是否应包含对象,发现 keypath 值已更改,并执行您期望的操作。

因此,如果您在 FRC 谓词中使用键路径,如果您更改该值,则需要返回 FRC 数组中的所有对象并“弄脏它们”,以便这些对象在通知中传递有关对象更改的信息。这很丑陋,但可能比保存或更改您的获取请求并重新获取更好。

我知道你不相信我,所以继续尝试吧。请注意,要使其正常工作,您必须知道 FRC 对象数组中的哪些项目会受到更改的影响,并“戳”它们以使 FRC 注意到更改。

正如我之前提到的,另一个选项是保存上下文并重新获取值。如果您不想保存上下文,则可以使 fetch 包含当前上下文中的更新,而无需从存储中刷新。

我发现伪造 FRC 正在监视的对象的更改是完成对作为其他实体的关键路径的谓词进行重新评估的最佳方法。

好的,所以,这是否是一个错误还有待商榷。就我个人而言,我认为如果 FRC 要监控密钥路径,它应该一直这样做,而不是像我们在这里看到的那样部分地进行。

我希望这是有道理的,我鼓励您提交错误报告。

于 2012-09-12T00:44:13.657 回答
5

你遇到了类似的问题。

我知道这个问题已经很老了,但我希望这对其他人有帮助:

最简单的方法是引入lastUpdated: NSDate在父对象中命名的新属性。

我有一个Conversation包含几个Messages. 每当更新消息的标志时,我都需要在仅显示sisRead的更新中进行更新。此外,in仅获取s 并且对 a 一无所知。ConversationOverviewViewControllerConversationNSFetchedResultsControllerConversationOverviewVCConversationMessage

每当有消息更新时,我都会打电话给message.parentConversation.lastUpdated = NSDate(). 这是手动触发更新的一种简单而有用的方法。

希望这可以帮助。

于 2016-02-16T11:06:05.533 回答
0

之后[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];可以保存托管对象上下文:

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

我怀疑您UITableView将在保存上下文后正确更新。这可能就是为什么将您的应用程序发送到后台有效的原因。我怀疑您的核心数据堆栈是在应用程序的委托中设置的,NSManagedObjectContext当它进入后台时,它会在 main 上执行保存。

于 2012-09-11T13:36:09.290 回答