简体   繁体   English

更改托管对象属性不会触发 NSFetchedResultsController 更新表视图

[英]Changing a managed object property doesn't trigger NSFetchedResultsController to update the table view

I have a fetchedResultsController with a predicate, where "isOpen == YES"我有一个带有谓词的fetchedResultsController ,其中“isOpen == YES”

When calling for closeCurrentClockSet , I set that property to NO .当调用closeCurrentClockSet 时,我将该属性设置为NO Therefore, it should no longer appear on my tableView.因此,它不应再出现在我的 tableView 上。

For Some Reason, this is not happening.出于某种原因,这不会发生。

Can someone help me figure this out please?有人可以帮我解决这个问题吗?

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

}

-- ——

I have a couple of methods more, using the exact same approach , by calling a custom fetchRequestForEntity:withPredicate:inManagedObjectContext method.我还有几个方法,使用完全相同的方法,通过调用自定义fetchRequestForEntity:withPredicate:inManagedObjectContext方法。

In those methods, when changing a property, tableView get correctly updated!在这些方法中,当更改属性时,tableView 会正确更新! But this one above ( closeCurrentClockSet ), doesn't!但是上面的这个( closeCurrentClockSet )没有! I can't figure out why.我不明白为什么。

-- ——

My implementation for my fetchedResultsController, is from Apple's documentation.我的 fetchedResultsController 实现来自 Apple 的文档。

Also, another detail.另外,还有一个细节。 If I send my App, to the background.如果我将我的应用程序发送到后台。 Close it and re-open, tableView shows updated as it should!关闭它并重新打开,tableView 显示它应该更新!

I have tried my best to follow previous questions here on stackOverflow.我已尽力在 stackOverflow 上关注以前的问题。 No luck.没运气。 I also NSLogged this to the bone.我也把它记录到了骨头上。 The object is getting correctly fetched.对象被正确获取。 It is the right one.这是正确的。 isOpen Property is being correctly updated to NO . isOpen 属性正在正确更新为NO But for some reason, my fetchedResultsController doesn't update tableView.但是由于某种原因,我的 fetchedResultsController 没有更新 tableView。

I did try a couple a "hammer" solutions, like reloadData and calling performFetch.我确实尝试了几个“锤子”解决方案,例如 reloadData 和调用 performFetch。 But that didn't work.但这没有用。 Or would make sense to used them...或者使用它们是有意义的......

EDIT: scratch that, it DID work, calling reloadData imediatly after performFetch on my resultsController but using reloadData is hammering a solution.编辑:从头开始,它确实有效,在我的 resultsController 上 performFetch 后立即调用 reloadData但使用 reloadData 正在敲定解决方案。 Plus, it takes out all animations.另外,它取出所有动画。 I want my controller to auto-update my tableView.我希望我的控制器自动更新我的 tableView。

Can someone help me figure this out?有人可以帮我解决这个问题吗?

Any help is greatly appreciated!任何帮助是极大的赞赏!

Thank you,谢谢,

Nuno努诺

EDIT:编辑:

The complete implementation.完整的实现。

fetchedResultsController is pretty standard and straightforward. fetchedResultsController 非常标准和简单。 Everything else is from Apple's documentation其他一切都来自 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;

}

-- ——

Boilerplate code from Apple's documentation: 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];
}

1ST UPDATE:第一次更新:

Tracking [managedObjectContext hasChanges] does return YES, as it should.跟踪[managedObjectContext hasChanges]确实返回 YES,因为它应该。 But fetchedResultsController doesn't update the tableView但是 fetchedResultsController 不会更新 tableView

2ND UPDATE第二次更新

didChangeObject:atIndexPath: does not get called for this particular case! didChangeObject:atIndexPath: 在这种特殊情况下不会被调用 I have 2 more methods, with the EXACT same code, they just happen to be a different entity.我还有 2 个方法,使用完全相同的代码,它们恰好是不同的实体。 And they work perfectly.他们工作得很好。 Thank you @Leonardo for pointing this out谢谢@Leonardo 指出这一点

3TH UPDATE this method, follows the same rules.第 3 次更新此方法,遵循相同的规则。 But does actually work.但确实有效。

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


}

Anyone has any other ideas for what I might be missing?任何人对我可能缺少的东西有任何其他想法吗?

Thank you,谢谢,

Nuno努诺

OK, I will explain your problem, then I will let you judge whether it is a bug in FRC or not.好的,我会解释你的问题,然后我让你判断它是否是FRC中的错误。 If you think it is a bug, then you really should file a bug report with apple.如果您认为这是一个错误,那么您真的应该向苹果提交错误报告。

Your fetch result controller predicate is like this:你的 fetch 结果控制器谓词是这样的:

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

which is a valid predicate for a boolean value.这是布尔值的有效谓词。 It is going to follow the relationship of the clockSet entity and grab its isOpen attribute.它将遵循clockSet实体的关系并获取其isOpen属性。 If it is YES then those objects will be accepted into the array of objects.如果为YES那么这些对象将被接受到对象数组中。

I think we are good up to here.我认为我们很好。

Now, if you change one of clockSet.isOpen attributes to NO , then you expect to see that object disappear from your table view (ie, it should no longer match the predicate so it should be removed from the array of fetched objects).现在,如果您将clockSet.isOpen属性之一更改为NO ,那么您希望看到该对象从您的表视图中消失(即,它不应再匹配谓词,因此应将其从获取的对象数组中删除)。

So, if you have this...所以,如果你有这个...

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

then, whichever top-level object has a relationship to the currentClockSet should "disappear" from your FRC array of fetched results.然后,任何与currentClockSet有关系的顶级对象都应该从您的 FRC 获取结果数组中“消失”。

However, you do not see it disappear.但是,您不会看到它消失。 The reason is that the object monitored by the FRC did not change.原因是FRC监控的对象没有变化。 Yes, the predicate key path changed, but the FRC holds entities of ClockPair and a ClockSet entity actually changed.是的,谓语关键路径改变,但FRC持有的实体ClockPairClockSet实际上改变实体。

You can watch the notifications fly around to see what's going on behind the scenes.您可以观看通知飞来飞去,看看幕后发生了什么。

Anyway, the FRC will use a key path when you do a fetch, but it will not monitor changes to objects that are not in its actual set of fetched objects.无论如何,当您执行提取时,FRC 将使用一个关键路径,但它不会监视对不在其实际提取对象集中的对象的更改。

The easiest work-around is to "set" an attribute for the object that holds this key path object.最简单的解决方法是为包含此关键路径对象的对象“设置”一个属性。

For example, I noticed that the ClockPair also has an isOpen attribute.例如,我注意到ClockPair也有一个isOpen属性。 If you have an inverse relationship, then you could do this...如果你有相反的关系,那么你可以这样做......

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

Notice that you did not actually change the value at all.请注意,您实际上根本没有更改该值。 However, the setter was called, which triggered KVO, and thus the private DidChange notification, which then told the FRC that the object changed.然而,setter 被调用,这触发了 KVO,从而触发了私有的 DidChange 通知,然后通知 FRC 对象已更改。 Thus, it re-evaluates the check to see if the object should be included, finds the keypath value changed, and does what you expect.因此,它会重新评估检查以查看是否应包含对象,发现键路径值已更改,并执行您期望的操作。

So, if you use a key path in your FRC predicate, if you change that value, you need to worm your way back to all the objects in the FRC array and "dirty them up" so that those objects are in the notification that is passed around about object changes.因此,如果您在 FRC 谓词中使用关键路径,如果您更改该值,您需要蠕虫返回 FRC 数组中的所有对象并“弄脏它们”,以便这些对象在通知中传递有关对象更改的信息。 It's ugly, but probably better than saving or changing your fetch request and refetching.这很丑陋,但可能比保存或更改您的获取请求并重新获取要好。

I know you don't believe me, so go ahead and try it.我知道你不相信我,所以去试试吧。 Note, for it to work, you have to know which item(s) in the FRC array of objects would be affected by the change, and "poke" them to get the FRC to notice the change.请注意,要使其工作,您必须知道 FRC 对象数组中的哪些项目会受到更改的影响,并“戳”它们以使 FRC 注意到更改。

The other option, as I mentioned earlier, is to save the context, and refetch the values.正如我之前提到的,另一个选项是保存上下文,然后重新获取值。 If you don't want to save the context, you can make the fetch include updates in the current context, without refreshing from the store.如果您不想保存上下文,则可以使提取包含当前上下文中的更新,而无需从存储中刷新。

I have found that faking a change to an object that the FRC is watching is the best way to accomplish a re-evalution of predicates that are key paths to other entities.我发现对 FRC 正在监视的对象进行伪造更改是重新评估谓词的最佳方法,谓词是通往其他实体的关键路径。

OK, so, whether this is a bug or not is up for some debate.好的,所以,这是否是一个错误还有待商榷。 Personally, I think if the FRC is going to monitor a keypath, it should do it all the way, and not partially like we see here.就我个人而言,我认为如果 FRC 将要监控一个关键路径,它应该一直这样做,而不是像我们在这里看到的那样。

I hope that make sense, and I encourage you to file a bug report.我希望这是有道理的,我鼓励您提交错误报告。

You ran into a similar problem.你遇到了类似的问题。

I know this question is pretty old but I hope this helps someone else:我知道这个问题已经很老了,但我希望这对其他人有所帮助:

The easiest way was to introduce a new property named lastUpdated: NSDate in the parent object.最简单的方法是在父对象中引入一个名为lastUpdated: NSDate的新属性。

I had a Conversation which contains several Messages .我有一个包含几条MessagesConversation Whenever the isRead flag of the message was updated, I needed an update in the ConversationOverviewViewController that only displays Conversation s.每当消息的isRead标志更新时,我都需要在ConversationOverviewViewController中进行更新,该更新仅显示Conversation Furthermore, the NSFetchedResultsController in the ConversationOverviewVC only fetches Conversation s and doesn't know anything about a Message .此外, ConversationOverviewVCNSFetchedResultsController只获取Conversation并且对Message一无所知。

Whenever a message was updated, I called message.parentConversation.lastUpdated = NSDate() .每当更新消息时,我都会调用message.parentConversation.lastUpdated = NSDate() It's an easy and useful way to trigger the update manually.这是手动触发更新的一种简单而有用的方法。

Hope this helps.希望这可以帮助。

After [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]]; [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]]; can you save the managed object context:你能保存托管对象上下文吗:

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

I suspect your UITableView will update properly after saving the context.我怀疑您的UITableView在保存上下文后会正确更新。 This is likely why sending your app to the background works.这可能是将您的应用程序发送到后台工作的原因。 I suspect that your Core Data stack is set up in the application's delegate in such a way that it performs a save on the main NSManagedObjectContext when it goes into the background.我怀疑您的 Core Data 堆栈是在应用程序的委托中设置的,它在进入后台时对主NSManagedObjectContext执行保存。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在NSFetchedResultsController中更改托管对象的属性 - Changing a property of a managed object in NSFetchedResultsController NSFetchedResultsController启动后不会更新UITableView - NSFetchedResultsController doesn't update UITableView after launch NSFetchedResultsController不排序 - NSFetchedResultsController doesn't sort iCloud和核心数据。 NSFetchedResultsController在初始同步时不会更新 - iCloud and Core Data. NSFetchedResultsController doesn't update on initial sync 将属性更新级联到相关的托管对象 - Cascading a property update to a related managed object iPhone:如何在添加新对象时更新显示托管对象的表视图 - Iphone: How to update a table view that displays managed objects when new object is added NSFetchedResultsController提供表视图,而同一持久存储的后台更新导致死锁 - NSFetchedResultsController feeding table view while background update of same persistent store causes deadlock 如果某个对象的某个属性不满足它的谓词,是否可以让NSFetchedResultsController删除一个对象? - Is it possible to have an NSFetchedResultsController delete an object if one of the object's properties doesn't fulfil it's predicate? 我可以将 NSFetchedResultsController 放在托管对象类中吗? - Can I put NSFetchedResultsController inside a Managed Object Class? 在更新为未提取的NSManagedObject之后,NSFetchedResultsController不调用controllerDidChangeContent: - NSFetchedResultsController doesn't call controllerDidChangeContent: after update to non-fetched NSManagedObject
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM