【问题标题】:UITableViewCell does not reflect changes after dismissing a modally presented ViewController关闭模态呈现的 ViewController 后 UITableViewCell 不反映更改
【发布时间】:2023-03-18 01:06:02
【问题描述】:

我有一个带有 TableView 的 UIViewController 用于显示“清单”项目。这个 ViewController 还有一个按钮,可以触发 segue 以模态方式呈现另一个 UIViewController,供用户添加新的清单项。

所有清单项目都存储在 CoreData 中。

新的CoreData 实体由SecondViewController 创建和存储。用户可以在点击触发解除SecondViewController 的按钮之前添加任意数量的checklist 项目。在这里,由于这个SecondViewController 是模态呈现的,我假设它的活动与FirstUIViewController 根本没有任何关系。

在包含UITableViewFirstViewController 处,有一个NSFetchedResultsController 再次在CoreData 执行获取请求并将所有数据提供给UITableView。 NSFetchedResultsController 的委托也指向这个FirstUIViewController

问题是,在SecondViewController被解雇后,UITableView似乎注意到了新清单实体的插入,并在UITableView中插入了新的UITableCell。然而,奇怪的是,这个新插入的行没有显示正确的cell.textlabel。仅显示标签上没有任何内容的空白UITableViewCell

下面是我如何配置UITableViewCell

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ChecklistCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
    ChecklistItem *item = [self.fetch_controller objectAtIndexPath:indexPath];
    cell.textLabel.text = item.name;    
}

这是我为 NSFetchedResultsControllerDelegate 提供的内容

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    switch (type) {
        case NSFetchedResultsChangeInsert:
            NSLog(@"*** controllerDidChangeObject - NSFetchedResultsChangeInsert");
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

强制[self.tableView reloadData] 似乎有帮助。我知道这一点,因为我添加了一个按钮来调用此方法,并且单元格的标签相应地刷新和更新。

因此,我尝试了这个,因为我希望它会在 SecondViewController 被解雇后被调用。但是,该方法被调用是因为 Reload Data! 已被记录,但不知何故 tableView 仍然没有正确显示新添加的单元格的标签。

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    [self performFetch];
    [self.tableView reloadData];
    NSLog(@"Reload Data!");
}

我想知道这里可能有什么问题。不知何故,self.tableView 在解雇SecondViewController 后不会重新加载。有什么建议吗?

##已编辑

好的,更奇怪的是我在- (void)viewDidAppear:(BOOL)animated 中尝试了[self.tableView reloadData]。新的 tableViewCell 仍然不能正确显示它的标签;然而,在等待 30 秒后,标签就神奇地弹出了!这是否意味着我的 iPhone/iPhone 模拟器需要时间思考?

【问题讨论】:

  • 您是否将数据保存在模态视图控制器中?
  • 是的,SecondViewController 处理所有的保存过程。 FirstViewController 只是获取并显示数据。
  • 你实现了controllerWillChangeContent并调用了[self.tableView beginUpdates]?在实现 controllerDidChangeContent 时,您还需要一个 [self.tableView endUpdates]。
  • @bbarnhart 我确实有他们两个。委托代码有点长,这里就不一一贴了。我想我需要想办法在所有新的清单项通过使用这些委托方法正确插入后得到通知。
  • 从 viewWillAppear 中删除所有代码。这些都不需要。接下来,将 NSLog 消息添加到插入期间将被命中的每个方法。此跟踪应该可以帮助您查看未调用的内容。

标签: ios uitableview core-data nsfetchedresultscontroller segue


【解决方案1】:

说明

您的核心数据保存需要时间。如果您正在保存数据,然后立即关闭模态视图控制器,则您在 performFetch 函数上存在竞争条件。这就是为什么您看到它在 viewWillAppear 函数中不起作用,但在您按下按钮时它起作用。它有时会在 viewDidAppear 中工作,但不能保证。这取决于您是否使用模拟器,以及系统的繁忙程度。

具体建议

由于你是从核心数据中拉取数据,我建议你使用NSFetchedResultsController并实现NSFetchedResultsControllerDelegate。这些是专门为将 coreData 绑定到 UITableViewController 而设计的。它侦听更改并根据这些更改立即添加、删除或更新行。

使用代码更新

要正确实现NSFetchedResultsControllerDelegate,您只需从该网站复制代码即可:http://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html

一些提示:

  1. 您需要对实现cellForRowAtIndexPath 的方式稍作更改。创建一个名为configureCell 的新方法。这是一个很好的模式,因此您可以在 Apple 和其他地方的许多示例代码中找到它。
  2. 从上面网站的“典型用途”部分复制代码。您不需要实施“用户驱动的更新”。一定要设置

    self.fetchedResultsController.delegate = self;

  3. 如果要检查这段代码是否正常工作,可以将断点或NSLogs放入controllerWillChangeContentcontroller:didChangeObject

  4. 您无需在第一次调用后再次调用 performFetch。此代码适当地侦听更改和更新。
  5. 我一直建议大家——我喜欢 MagicalRecord——所以使用它。

配置单元

下面的 configureCell:atIndexPath 方法由 NSFetchedResultsController 委托调用。

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    Product *product = [_fetchedResultsController objectAtIndexPath:indexPath];
    cell.textLabel.text = product.name;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"Product Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    [self configureCell:cell atIndexPath:indexPath];

    return cell;
}

在此处查看委托实现代码。查看对象更新时如何调用 configureCell。

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    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 {

    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath]
                    atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
}

【讨论】:

  • 嗨 Eddie,我确实使用了 NSFetchedResultsController 及其委托来处理获取。有没有办法在再次调用 performFetch 之前检查并等待所有数据是否已正确插入?
  • 事实上,委托方法很有效,因为它知道新数据已被插入,因此 tableview 添加新行。但是,单元格标签没有正确显示。
  • @Kann 如果 NSFetchedResultsControllerDelegate 被正确实现,你不想在第一次之后再调用 performFetch 。 brb 与另一条评论。
  • @Kann,我更新了上面的答案,包括正确实施委托和检查它是否工作的提示。关于单元格标签未正确显示的注释 - 您必须确保单元格格式和更新代码位于“configureCell”中。这就是 FetchedResults 委托更新单元格的方式。
  • 您好 Eddie,感谢您的提示,但我担心我可能仍然不太了解实现新 configureCell 和 ConfigureCellAtInsexPath 的需要之间的区别。我的意思是,我在方法中所做的只是从清单项中分配标签文本。 :$
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-03-07
  • 2019-11-15
  • 1970-01-01
  • 2018-06-25
  • 1970-01-01
  • 1970-01-01
  • 2018-03-14
相关资源
最近更新 更多