我现在一直在努力工作几个小时和几天。视图控制器中的某些内容会在发生更改时立即更新,而某些内容则不会更新。举个例子,我会尽量复制尽可能多的代码供您理解。
我正在为我的公司构建一个预算应用程序。三个实体是:MainCategory
,SpendingCategory
和Expenditures
。支出类别有几种支出,主要类别有几种支出类别。
MainCategory
< - >> SpendingCategory
< - >> Expenditures
编辑和添加交易发生在TransactionViewController
。作为模型,我有一个SpendingCategory
。您可以从主类别视图控制器访问此视图控制器,然后在prepareForSegue方法中设置SpendingCategory
。
以下是TransactionViewController
的必要内容(我省略了viewDidLoad
,因为只设置了标签和格式):
#pragma mark Model initialization
- (void)setSpendingCategory:(SpendingCategory *)spendingCategory
{
_spendingCategory = spendingCategory;
[self setupFetchedResultsController];
self.title = NSLocalizedString(@"Transactions", nil);
}
- (SpendingCategory *)spendingCategory
{
return _spendingCategory;
}
- (void)setupFetchedResultsController
{
NSDate *startDate = [NSDate startDateForCostPeriod:[self.spendingCategory getBiggestCostPeriod]];
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Expenditures"];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"date" ascending:NO]];
request.predicate = [NSPredicate predicateWithFormat:@"forSpendingCategory = %@ AND date >=%@", self.spendingCategory, startDate];
self.fetchedResultsController = [[NSFetchedResultsController alloc]initWithFetchRequest:request
managedObjectContext:self.spendingCategory.managedObjectContext
sectionNameKeyPath:nil cacheName:nil];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateBudgetAndBudgetLeft];
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:NO];
}
- (void)updateBudgetAndBudgetLeft
{
self.budgetLabel.text = [NSString stringWithFormat:@"%@/%@",[self.spendingCategory convertCostToBiggestCostPeriodAsString], [self.spendingCategory getBiggestCostPeriodAsString]];
double moneySpent = [[self.spendingCategory getExpendituresAmountForCostPeriod:[self.spendingCategory getBiggestCostPeriod]] doubleValue];
double budget = [[self.spendingCategory convertCostToBiggestCostPeriod] doubleValue];
//Money spent will be ADDED because negative values are already stored as negative
double budgetLeftCalc = budget + moneySpent;
if(budgetLeftCalc >= 0){
NSNumber *budgetLeft = [NSNumber numberWithDouble:budgetLeftCalc];
self.budgetLeftLabel.text = [NSString stringWithFormat:@"%@ %@", [budgetLeft getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(@"left", nil)];
} else {
NSNumber *budgetLeft = [NSNumber numberWithDouble:-budgetLeftCalc];
self.budgetLeftLabel.text = [NSString stringWithFormat:@"%@ %@", [budgetLeft getLocalizedCurrencyStringWithDigits:0], NSLocalizedString(@"over", nil)];
self.budgetLeftLabel.textColor = [UIColor redColor];
}
if (moneySpent < 0) {
self.progressBar.progress = -moneySpent/budget;
} else {
self.progressBar.progress = 0;
}
}
从数据库中获取每期成本的函数是:(这显然没有及时得到正确的结果):
- (NSNumber *)getExpendituresAmountForCostPeriod:(CostPeriod)costPeriod
{
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Expenditures"];
[fetchRequest setResultType:NSDictionaryResultType];
NSDate *startDate = [NSDate startDateForCostPeriod:[self getBiggestCostPeriod]];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"forSpendingCategory = %@ AND date >= %@", self, startDate];;
//Define what we want
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath: @"amount"];
NSExpression *sumExpression = [NSExpression expressionForFunction: @"sum:"
arguments: [NSArray arrayWithObject:keyPathExpression]];
//Defining the result type (name etc.)
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName: @"totalExpenditures"];
[expressionDescription setExpression: sumExpression];
[expressionDescription setExpressionResultType: NSDoubleAttributeType];
// Set the request's properties to fetch just the property represented by the expressions.
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Execute the fetch.
NSError *error = nil;
NSArray *objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (objects == nil) {
return [NSNumber numberWithDouble:0];
} else {
if ([objects count] > 0) {
return [[objects objectAtIndex:0] valueForKey:@"totalExpenditures"];
} else {
return [NSNumber numberWithDouble:0];
}
}
}
如果您点击某个交易,则会转到ExpenditureViewController
,该Expenditure
具有date
对象*支出的模型。此模型有三个属性:amount
,description
和updateBudgetAndBudgetLeft
。
如果您现在更改某些交易或插入新交易,则此TransactionViewController
不会执行任何操作!奇怪的是,经过一段时间后确实如此。如果等待20秒并重新加载TransactionViewController
,则会发生更改。然而,我NSFetchedResultsController
中显示的交易完全准确!为什么?为什么会延迟?为什么我甚至需要做一个单独的函数updateBudgetAndBudgetLeft,因为我认为我的CoredDataViewController
会自动执行它?!在整个核心数据丛林中我会错过什么?
编辑2:
完成- (void)performFetch
{
if (self.fetchedResultsController) {
if (self.fetchedResultsController.fetchRequest.predicate) {
if (self.debug) NSLog(@"[%@ %@] fetching %@ with predicate: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName, self.fetchedResultsController.fetchRequest.predicate);
} else {
if (self.debug) NSLog(@"[%@ %@] fetching all %@ (i.e., no predicate)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), self.fetchedResultsController.fetchRequest.entityName);
}
NSError *error;
[self.fetchedResultsController performFetch:&error];
if (error) NSLog(@"[%@ %@] %@ (%@)", NSStringFromClass([self class]), NSStringFromSelector(_cmd), [error localizedDescription], [error localizedFailureReason]);
} else {
if (self.debug) NSLog(@"[%@ %@] no NSFetchedResultsController (yet?)", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
}
[self.tableView reloadData];
}
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
NSFetchedResultsController *oldfrc = _fetchedResultsController;
if (newfrc != oldfrc) {
_fetchedResultsController = newfrc;
newfrc.delegate = self;
if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
self.title = newfrc.fetchRequest.entity.name;
}
if (newfrc) {
if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
[self performFetch];
} else {
if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self.tableView reloadData];
}
}
}
#pragma mark - UITableViewDataSource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[self.fetchedResultsController sections] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[self.fetchedResultsController sections] objectAtIndex:section] name];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [self.fetchedResultsController sectionIndexTitles];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
if(!self.reordering){
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext) {
[self.tableView beginUpdates];
self.beganUpdates = YES;
}
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
if(!self.reordering){
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if(!self.reordering){
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if(!self.reordering){
if (self.beganUpdates) [self.tableView endUpdates];
}
}
- (void)endSuspensionOfUpdatesDueToContextChanges
{
_suspendAutomaticTrackingOfChangesInManagedObjectContext = NO;
}
- (void)setSuspendAutomaticTrackingOfChangesInManagedObjectContext:(BOOL)suspend
{
if (suspend) {
_suspendAutomaticTrackingOfChangesInManagedObjectContext = YES;
} else {
[self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:0];
}
}
-(void)stopSuspendAutomaticTrackingOfChangesInManagedObjectContextWithDelay:(double)delay
{
//the delay is necessary in my app after row editing
[self performSelector:@selector(endSuspensionOfUpdatesDueToContextChanges) withObject:0 afterDelay:delay];
}
(因为我使用嵌入式表视图,tableView是一个属性)
{{1}}
答案 0 :(得分:1)
我会尝试提供一些提示,但由于代码太多,因此很难遵循逻辑。
首先,你告诉updateBudgetAndBudgetLeft
没有做任何事情,但我看不出这个方法在任何地方被调用。如果viewWillAppear
是调用该方法的唯一位置,则重新计算数据是不够的。事实上,你可以改变东西,但控制器不应该从屏幕上消失......这只是我的假设。
第二,在
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if(!self.reordering){
if (self.beganUpdates) [self.tableView endUpdates];
}
}
你错过了检查self.suspendAutomaticTrackingOfChangesInManagedObjectContext
bool值。
第三,为了解决问题,我建议忽略CoreDataViewController
。这个类,我认为它被用作基类,可能会引入难以调试的问题。特别是对于核心数据新手。所以,暂时忘掉它。
基于这些假设,我要说的是应该明确调用updateBudgetAndBudgetLeft
。当您使用NSFetchedResultsController
时,它会跟踪您设置的实体(可以在NSFetchedResultsController pitfall中找到有趣的解释)。我想在这种情况下Expenditures
,但没有人会调用update…
方法。
例如,我的建议是在- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
中调用该方法。在那里,您确定已完成更改以进行跟踪。
修改1
你错过了检查 self.suspendAutomaticTrackingOfChangesInManagedObjectContext bool 值。你在这里是什么意思?
如果您暂停跟踪更改,则不应调用endUpdates
方法。如果我记得很清楚,斯坦福代码也会做类似的事情。检查一下。
所以你的建议是覆盖这个controllerDidChangeContent 在每个视图控制器中(它是子类化CoreDataViewController) 并在那里调用updateBudgetAndBudgetLeft?
是。这可能是一种选择。同时调用super
也应该有效,但在这种情况下,即使您不想跟踪更改,也需要更新。无论如何,我会在没有基类的情况下工作。
为什么在viewWillAppear中调用此方法是不够的?我的意思是 每次视图可见时都应该调用它?
这取决于您重新计算数据的时间。如果您执行它并且控制器保持可见,则不会调用update…
。
修改2
根据文档并基于此新问题iOS FetchRequest on aggregate functions: How to include pending changes?,如果update…
方法与NSDictionaryResultType
类型一起使用,则必须先执行save
。之后,可以正常调用它。换句话说,每次修改Expenditures
项时,都应该save
后跟update…
。显然,节省太多会打击磁盘,因此可能导致应用程序变慢,但在这里你需要测量。否则,您可以在内存级别执行聚合。要做到这一点,只需过滤正确的实体即可。这可以在controllerDidChangeContent
或viewWillAppear
轻松完成。