自定义 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,用于管理UITableViewNSFetchedResultsController

更新

实现这些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 中的更改

UITableview 默认单元格中的自定义分隔符显示意外行为

UITableView 中自定义单元格中的导航控制器和 UIImageView