自定义 UITableView 单元格中的 UILabel 未随核心数据更改而更新
Posted
技术标签:
【中文标题】自定义 UITableView 单元格中的 UILabel 未随核心数据更改而更新【英文标题】:UILabel in custom UITableView cell not updating with Core Data change 【发布时间】:2014-12-03 15:05:54 【问题描述】:我正在尝试构建一个游戏计分应用程序,该应用程序使用带有玩家照片、姓名、按钮等的自定义表格单元格...直接在表格视图的自定义单元格中添加/减去按钮正在点击我的保存方法,并将其存储回该特定用户的 Core Data 中。
问题在于屏幕上的分数没有更新和反映变化。完成对 Core Data 的保存操作后,我将调用 [self.tableView reloadData];... 什么都没有。但是,如果我重新启动应用程序,则会出现分数变化(对于我点击的任何球员)。
也许我让这件事变得比需要的更难,或者我只是没有抓住真正的问题。
想法/cmets? 提前感谢负载。 :-)
对不起,如果这有点矫枉过正,但这是我的大部分实现文件:
- (void)viewWillAppear:(BOOL)animated
[super viewWillAppear:animated];
[self resetViews];
- (void)viewDidLoad
[super viewDidLoad];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
[context setUndoManager:nil];
_managedObjectContext = context;
self.tableView.delegate = self;
[self setNeedsStatusBarAppearanceUpdate];
-(void)resetViews
NSLog(@"\n\n\nresetViews()");
[self setupFetchedResultsController];
[self.tableView reloadData];
[self.view setNeedsDisplay];
- (void)setupFetchedResultsController
NSString *entityName = @"Players";
NSLog(@"Setting up a Fetched Results Controller for the Entity named %@", entityName);
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.sortDescriptors = [NSArray arrayWithObject:
[NSSortDescriptor
sortDescriptorWithKey:@"playerName"
ascending:YES
selector:@selector(localizedCaseInsensitiveCompare:)]];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:request error:&error];
_playerArray = [[NSMutableArray alloc]initWithArray:results];
NSLog(@"_playerArray count: %i", [_playerArray count]);
NSLog(@"\n");
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
return _playerArray.count;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
return 1;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
static NSString *cellIdentifier = @"playerCell";
ScoringCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Configure the cell...
Players *player_info = [_playerArray objectAtIndex:indexPath.row];
NSSet *score = player_info.scores;
for (Scoring *perObj in score)
cell.lblPlayerScore.text = [perObj.score stringValue];
NSLog(@"\n\n\n score for %@: %@", player_info.playerName, perObj.score);
cell.lblPlayerName.text = player_info.playerName;
cell.lblPlayerNickName.text = player_info.playerNickName;
cell.btnIncreaseScore.tag = indexPath.row;
cell.btnDecreaseScore.tag = indexPath.row;
cell.imgPlayerPhoto.image = [UIImage imageNamed:@"tmp_playerImage"];
return cell;
- (IBAction)increaseScore:(id)sender
NSLog(@"PageContentViewController: increaseScore()");
UIButton* btn=(UIButton*)sender;
int selectedPlayerInt = btn.tag;
//NSLog(@"Selected row is: %d",btn.tag);
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:@"add"];
- (IBAction)decreaseScore:(id)sender
NSLog(@"PageContentView: decreaseScore()");
UIButton* btn=(UIButton*)sender;
int selectedPlayerInt = btn.tag;
//NSLog(@"Selected row is: %d",btn.tag);
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:@"subtract"];
-(void)updateRowScore: (Players *)player_info :(NSString *)modifier
NSLog(@"\n\nupdateRowScore()");
NSLog(@"Update score (%@) for: %@\n", modifier, player_info.playerName);
NSArray *scoreDataArray;
if ([self playerScoreCount:player_info] == 0)
// NEW score... we've never scored before.
Scoring *scoring_data = [NSEntityDescription
insertNewObjectForEntityForName:@"Scoring"
inManagedObjectContext:_managedObjectContext];
//Since this is the first score, always set it to 1
scoring_data.score = [NSNumber numberWithInt:1];
scoring_data.holeNumber = [NSNumber numberWithInt:_pageIndex];
scoring_data.scoredBy = player_info;
else
//Update existing player score..
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *BEntity = [NSEntityDescription entityForName:@"Scoring" inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:BEntity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"(scoredBy = %@)", [player_info objectID]];
[fetchRequest setPredicate:predicate];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
scoreDataArray = [[NSMutableArray alloc]initWithArray:results];
Scoring *score_update = [scoreDataArray objectAtIndex:0];
int currentScore = [score_update.score intValue];
NSLog(@"current score: %d", currentScore);
if ([modifier isEqual: @"add"])
currentScore++;
else
// Don't allow negative scores.
if (currentScore >= 1)
currentScore--;
else
currentScore = 0;
NSLog(@"NEW score: %d", currentScore);
score_update.score = [NSNumber numberWithInt:currentScore];
// write to database
[self.managedObjectContext save:nil];
[self resetViews];
更新: 感谢bbarnhart 的提示...我之前已阅读过该帖子并将其用作我开始的基础。决定更进一步,使用更多 Ray Wenderlich 示例重构一段代码。
我已经看到对正在记录的内容进行了一些改进,并通过 NSLog 进行了报告……但视图仍然没有改变。
该操作正在增加分数,然后我正在使用[self configureCell:cell atIndexPath:path];
重置单元格在那里...负责将文本发送到显示器的方法... NSLog 显示2014-12-04 22:40:40.199 appName[7153:150248] Score for Tim: 4
当display 仍然只显示 3。
我知道这是一些愚蠢的菜鸟举动...我只是在做一些我无法弄清楚的完全错误的事情。这是修改后的代码的sn-p。
- (NSFetchedResultsController *)fetchedResultsController
if (_fetchedResultsController != nil)
return _fetchedResultsController;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"Players"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:@"playerName" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:_managedObjectContext
sectionNameKeyPath:nil
cacheName:@"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
NSError *error;
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
_playerArray = [[NSMutableArray alloc]initWithArray:results];
NSLog(@"_playerArray count: %i", [_playerArray count]);
return _fetchedResultsController;
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
id sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
static NSString *cellIdentifier = @"playerCell";
ScoringCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
cell = [[ScoringCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
[self configureCell:cell atIndexPath:indexPath];
return cell;
- (void)configureCell:(ScoringCell *)cell atIndexPath:(NSIndexPath *)indexPath
Players *player_info = [_fetchedResultsController objectAtIndexPath:indexPath];
NSSet *scoreSet = player_info.scores;
NSString *cell_score;
for (Scoring *scoreObj in scoreSet)
cell_score = [scoreObj.score stringValue];
NSLog(@"Score for %@: %@", player_info.playerName, cell_score);
if (cell_score != nil)
cell.lblPlayerScore.text = cell_score;
cell.lblPlayerName.text = player_info.playerName;
cell.lblPlayerNickName.text = player_info.playerNickName;
cell.btnIncreaseScore.tag = indexPath.row;
cell.btnDecreaseScore.tag = indexPath.row;
cell.imgPlayerPhoto.image = [UIImage imageNamed:@"demo_playerb"];
[self resetViews];
NSLog(@"\n");
- (IBAction)increaseScore:(id)sender
NSLog(@"PageContentViewController: increaseScore()");
UIButton *senderButton = (UIButton *)sender;
int selectedPlayerInt = senderButton.tag;
NSIndexPath *path = [NSIndexPath indexPathForRow:senderButton.tag inSection:0];
Players *player_info = [_playerArray objectAtIndex:selectedPlayerInt];
[self updateRowScore:player_info:@"add":selectedPlayerInt:path];
-(void)updateRowScore:(Players *)player_info :(NSString *)modifier :(int)selectedPlayerInt :(NSIndexPath *)path
NSArray *scoreDataArray;
if ([self playerScoreCount:player_info] == 0)
// NEW score... we've never scored before.
Scoring *scoring_data = [NSEntityDescription
insertNewObjectForEntityForName:@"Scoring"
inManagedObjectContext:_managedObjectContext];
//Since this is the first score, always set it to 1
scoring_data.score = [NSNumber numberWithInt:1];
scoring_data.holeNumber = [NSNumber numberWithInt:_pageIndex];
scoring_data.scoredBy = player_info;
else
//Update existing player score..
NSError *error = nil;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *BEntity = [NSEntityDescription entityForName:@"Scoring"
inManagedObjectContext:_managedObjectContext];
[fetchRequest setEntity:BEntity];
NSPredicate *predicate = [NSPredicate
predicateWithFormat:@"(scoredBy = %@)", [player_info objectID]];
[fetchRequest setPredicate:predicate];
NSArray *results = [_managedObjectContext executeFetchRequest:fetchRequest error:&error];
scoreDataArray = [[NSMutableArray alloc]initWithArray:results];
Scoring *score_update = [scoreDataArray objectAtIndex:0];
int currentScore = [score_update.score intValue];
NSLog(@"current score: %d", currentScore);
if ([modifier isEqual: @"add"])
currentScore++;
else
// Don't allow negative scores.
if (currentScore >= 1)
currentScore--;
else
currentScore = 0;
NSLog(@"NEW score: %d", currentScore);
score_update.score = [NSNumber numberWithInt:currentScore];
// write to database
[self.managedObjectContext save:nil];
static NSString *cellIdentifier = @"playerCell";
ScoringCell *cell = [_tableView dequeueReusableCellWithIdentifier:cellIdentifier];
[self configureCell:cell atIndexPath:path];
[self resetViews];
---------
更新: 自从我有机会重新访问以来已经有一段时间了,并且在启用您的提示后刚刚注意到一个新问题。当在列表中向下或向上滚动并拉出正常边界时,tableview 数据似乎会覆盖当前行上方或下方的行的显示。奇怪...不确定这个动画 Gif 是否会出现在 Stack 中。这是一个例子:
【问题讨论】:
自我注意:configureCell 中没有reloadRowsAtIndexPaths
... 很多加载异常会开始(参见上面的动画示例):-)
【参考方案1】:
您的表格视图没有动态更新的主要原因是NSFetchedResultsController
在发生更改时使用委托进行通知。您需要设置该委托 self.fetchedResultsController.delegate = self
,然后添加委托方法。
这是一个link to an example,用于管理UITableView
和NSFetchedResultsController
。
更新
实现这些NSFetchResultsController
委托方法以允许动态更新您的表。
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath: (NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
通常,这些方法包含用于更新表格的样板代码,您也可以在上面的链接中找到这些代码。
【讨论】:
除了我上面的编辑/更新...我发现了一些奇怪的东西。如果我点击添加或删除按钮,它仍会保存到 Core Data,但不会更新我在屏幕上看到的内容。但是......如果我拉下列表,所以“蒂姆”的分数暂时不在视野中......当我放手并且它弹回来时,视图上的分数就会更新。 当您添加self.fetchedResultsController.delegate = self
时,它会导致NSFetchedResultsController
观察者更改基础数据。您仍然需要让您的 UITableView
动态反映任何更改。
嗯——我认为这就是我通过调用 [self.tableView reloadData] 所做的。我想我会继续闲逛。感谢您的帮助... :-)
reloadData 将重新加载所有数据。您只想更新已更改的单元格。带有委托方法的示例就是您想要的。
这也是一个陷阱。很高兴你明白了。以上是关于自定义 UITableView 单元格中的 UILabel 未随核心数据更改而更新的主要内容,如果未能解决你的问题,请参考以下文章
更改单元格中的数据后,UITableView 中的自定义单元格将无法正确重新加载
自定义 UITableView 单元格中的 UILabel 未随核心数据更改而更新
从多节 UITableView 中的自定义静态单元格中检索标签文本
如何使自定义单元格中的 UILabel 显示保存在 UITableView 中的更改