在UITableView中保留占位符单元格

时间:2011-01-05 04:56:27

标签: iphone uitableview

我有一个UITableView,我永远不想低于1个单元格:这是一个目录读取,如果目录中没有文件,它有一个单元格,上面写着“No Files”。 (在编辑模式下,创建文件有一个奖励单元格,因此编辑模式永远不会低于两个单元格。)

现在可能只是缺乏睡眠让我无法从书包中解脱出来,但我一直在绊倒这样的错误:

*** Terminating app due to uncaught exception 
'NSInternalInconsistencyException', reason: 'Invalid update: 
invalid number of sections.  The number of sections contained 
in the table view after the update (2) must be equal to 
the number of sections contained in the table view before 
the update (2), plus or minus the number of sections inserted 
or deleted (1 inserted, 0 deleted).'

这是因为我在删除最后一个文件之前抢先添加“No Files”占位符。如果我在添加占位符之前删除最后一个文件单元格,就会发生崩溃。无论哪种方式,单元格计数与numberOfRowsInSection的返回都不同步,并且会触发崩溃。

肯定有这种情况的设计模式。告诉我吗?

3 个答案:

答案 0 :(得分:3)

按照以下代码段中显示的内容执行操作:

  • 首先从数组中删除行的数据
  • 如果数组项目未降至零,则从表中删除行
  • 如果数组项目已降为零,则重新加载表 - 注意:您的代码现在应该为行数提供1,并在调用tableview委托方法时将行0的单元格配置为显示“No Files”。
 
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

    if (editingStyle == UITableViewCellEditingStyleDelete) {
        // First delete the row from the data source
        [self deleteTableData: indexPath.row];  // method that deletes data from 
        if ([self.tableDataArray count] != 0) {
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        } else {
            [self.tableView reloadData];
        }

    }   
}

答案 1 :(得分:3)

我有另一个解决方案(似乎有很多方法可以做到这一点)。我发现其他可用的解决方案并不适合我的需求,所以我想出了这个。

这种方法非常简单,对于那些想要使用两个(或更多)占位符单元格的人来说非常好,不想搞乱你的数据模型或多个表视图,并且< em>特别是 - 需要允许删除单元格并将其插入表格中。它还允许显示和隐藏占位符的非常好的进出过渡。

听起来不错?好!让我们看看它的基本要点:

大多数占位符单元格解决方案存在的真正问题是,当您在桌面上允许编辑 - 删除和插入时,它们通常会失败。那,或者你必须开始处理处理编辑的代码,这可能会使一切更加混乱。这里的问题通常出现在numberOfRowsInSection方法中返回不一致的值。 tableview通常会遇到问题,例如,删除表中剩下一个单元格的单元格,删除后仍然有一个单元格(反之亦然,插入)。

简单的解决方案?我们总是有一致数量的单元格 - 数据源中的条目数,加上占位符的条目数。占位符单元格始终存在,只显示或隐藏其内容,具体取决于它是否技术上应该在那里。

尽管记录很长,但实现这一点实际上非常简单。让我们开始吧:

<强> 1。设置占位符单元原型: 这非常简单。在您的故事板中为占位符设置原型单元格。在我的情况下,我使用两个:一个显示&#34;加载......&#34;当桌子从服务器获取数据时,一个用于显示&#34;点击+上方添加项目&#34;当桌子上什么都没有的时候。

视觉设置你的细胞但是你喜欢(我只使用了字幕细胞样式,并将我的占位符文字放在字幕标签中。如果你这样做,不要忘记删除其他标签的文字) 。确保指定重用标识符,并将选择样式设置为“无”#。

<强> 2。设置numberOfRowsInSection委托方法: 这个方法现在将做两件基本的事情: 首先是返回数据源中的行数(加上我们占位符的行数),其次是根据需要显示/隐藏占位符的文本。这是启动此操作的好地方,因为每次删除或插入单元格时都会调用此方法(实际两次;编辑前一次,后一次)。为避免每次调用方法时都运行动画,我们将使用BOOL placeholderIsHidden来跟踪占位符的当前状态。我们还会在短暂延迟后执行切换,以允许单元格编辑动画开始。将此代码添加到您的班级:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int count = [self.dataSource count];

    // Hide/Show placeholder cell
    if (count == 0) {   // Placeholder should be shown
        if (self.placeholderIsHidden) {
            [self performSelector:@selector(animatePlaceholderCellChangeForIndexPath:) withObject:[NSIndexPath indexPathForRow:count inSection:0] afterDelay:0.1];
        }
    }
    else {   // Placeholder should be hidden
        if (!self.placeholderIsHidden) {
            [self performSelector:@selector(animatePlaceholderCellChangeForIndexPath:) withObject:[NSIndexPath indexPathForRow:count inSection:0] afterDelay:0.1];
        }
    }

    return count + 1;
}

很高兴去!现在让我们添加animatePlaceholderCellChange方法:

- (void)animatePlaceholderCellChangeForIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.8];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationBeginsFromCurrentState:YES];

    if (indexPath.row == 0) {
        cell.detailTextLabel.hidden = NO;
        self.placeholderIsHidden = NO;
    }
    else {
        cell.detailTextLabel.hidden = YES;
        self.placeholderIsHidden = YES;
    }

    [UIView commitAnimations];
}

此方法利用动画块,平滑显示和隐藏占位符之间的过渡(并使其与单元格编辑动画完美匹配)。

倒数第二,我们需要设置我们的cellForRowAtIndexPath方法来返回正确的单元格。

第3。设置cellForRowAtIndexPath 这很简单。在原型单元标识符声明之后,以及在进行正常的单元设置之前,将此代码放入您的方法中:

// Add a placeholder cell while waiting on table data
int nodeCount = [self.dataSource count];

if (indexPath.row == nodeCount) {
     // Place the appropriate type of placeholder cell in the first row
     if (self.isLoading) {
         return [tableView dequeueReusableCellWithIdentifier:LoadingCellIdentifier];
     }
     else {
         return [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
     }
 }

最后,让我们设置我们的BOOL属性,以跟踪数据是否正在加载,以及是否隐藏了占位符。

<强> 4。设置isLoadingplaceholderIsHidden

首先,将两个声明添加到您的班级.h文件中:

@property (assign, nonatomic) BOOL isLoading;
@property (assign, nonatomic) BOOL placeholderIsHidden;

现在,在viewDidLoad方法中设置初始值:

self.isLoading = YES;
self.placeholderIsHidden = NO;

那是placeholderIsHidden财产的!对于isLoading属性,您希望在代码开始从服务器加载数据的任何位置将其设置为YES,并在该操作完成的任何位置NO。在我的情况下,它非常简单,因为我在加载操作完成时使用带回调的操作。

那就是它!运行你的代码,看看整洁,流畅的动画和编辑安全的占位符单元格!

希望这有助于某人!

编辑:更重要的一点!请注意,您应该对代码执行一个更多的事情,以避免任何问题讨厌崩溃:讨论你的代码,以及你从数据源访问元素的任何地方(通常使用objectAtIndex:),确保你永远不会尝试拉出一个不存在的元素。

在少数几种情况下,这可能是一个问题,例如,您有代码可以访问屏幕上所有可见行的数据源中的元素(因为占位符可见,您可能有一个indexPath与数据源中的任何元素无关)。 (在我的情况下,这是一个特殊的代码,一旦表格视图完成滚动,就会在所有可见行上开始图像下载)。解决这个问题非常简单:在您从数据源访问元素的任何地方,在代码周围添加if语句,如下所示:

int count = [self.dataSource count];

if (indexPath.row != count) {

   // Code that accesses the element
   // ie:
   MyDataItem *dataItem = [self.dataSource objectAtIndex:indexPath.row];
}
祝你好运!

答案 2 :(得分:1)

绝对最简单的方法是实际拥有两个UITableViews

e.g。

UITableView * mainTable; 
UITableView * placeholderTable;

然后你在你的代表中做了像

这样的事情
tableView:(UITableView*)tableview cellForRowAtIndexPath:(NSIndexPath*)indexPath {
  if (tableview == placeholderTable){
    // code for rendering placeholder cell
  }
  else {
    // code for rendering actual table content
  }
}

无论何时插入/删除实体或初始化视图,请检查空状态并隐藏/取消隐藏表:

// something changed the number of cells
    if (tableIsEmpty){
      mainTable.hidden = YES;
      placeholderTable.hidden = NO;
    }
    else {
      mainTable.hidden = NO;
      placeholderTable.hidden = YES;
    }

这样做可以为你节省一点悲伤;特别是在使用Core Data NSFetchedResultsController和表视图动画时。