UITableView设置为静态单元格,是否可以通过编程方式隐藏一些单元格?

136

UITableView 设置为静态单元格。

是否可以通过编程的方式隐藏其中一些单元格?

22个回答

170

隐藏UITable中的静态单元格:

  1. 添加以下方法:

在您的UITableView控制器代理类中:

Objective-C:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell* cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];

    if(cell == self.cellYouWantToHide)
        return 0; //set the hidden cell's height to 0

    return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}

Swift:

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    var cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)

    if cell == self.cellYouWantToHide {
        return 0
    }

    return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}

这种方法会为UITable中的每个单元格调用。一旦它为您想要隐藏的单元格调用了它,我们就将其高度设置为0。我们通过为目标单元格创建一个outlet来识别目标单元格:

  1. 在设计器中,为您想要隐藏的单元格创建一个outlet。 一个这样的单元格的outlet名称在上面被称为"cellYouWantToHide"。
  2. 在IB中选择“剪切子视图”以隐藏您想要隐藏的单元格。 您要隐藏的单元格需要将ClipToBounds设置为YES。否则,文本将堆积在UITableView中。

10
好的解决方案。为了避免ClipToBounds问题,您也可以将单元格设置为隐藏。这样看起来更加清晰简洁。;-) - MonsieurDart
14
+1 表示设置 ClipToBounds 为 YES。其他帖子中的许多解决方案都没有注意到这一点。 - Jeff
2
很棒的想法。这是我找到的最简单的解决方案。其他解决方案在两个或更多位置有代码。顺便说一句,在IB中,ClipToBounds被列为“剪辑子视图”。 - VaporwareWolf
1
只需要进行一点小修正,使用 self.cellYouWantToHide 而不是 this.cellYouWantToHide。 - Zoltan Vinkler
2
我们可以使用“index.row”来隐藏“uitableviewcell”,而无需创建单元格对象。 - g212gs
显示剩余4条评论

49
你正在寻找这个解决方案:
StaticDataTableViewController 2.0

https://github.com/xelvenone/StaticDataTableViewController

这个功能可以展示/隐藏/重新加载任何静态单元格,可以选择是否使用动画效果!

[self cell:self.outletToMyStaticCell1 setHidden:hide]; 
[self cell:self.outletToMyStaticCell2 setHidden:hide]; 
[self reloadDataAnimated:YES];

请注意始终仅使用(reloadDataAnimated:YES/NO)(不要直接调用[self.tableView reloadData])
(不使用将高度设置为0的hacky解决方案,可以让您动画更改并隐藏整个部分)

6
这种解决方案的一个问题是,它会留下隐藏单元所在的空白。 - Jack
1
我的意思是,如果你有三个部分,然后隐藏了中间部分的单元格,那么该部分所占用的空间仍然存在。我没有尝试过示例项目,但我尝试了代码。我会试试这个示例。 - Jack
1
看看这个示例,有一个更大的更新,也许你使用了一些旧代码... 对我来说很完美。 - Peter Lapisu
我不确定我理解你的意思,就我所看到的,我的代码和示例之间唯一的区别是,我使用了IBOutletCollection而不是为每个单元格使用单独的outlet。 - Jack
我看了一下,很不错的补充。只是在你的示例中,如果显示/隐藏部分按钮与第一部分一起工作,可能会更具说明性,因为这将显示它确实没有留下间隙(否则就像我之前误解的那样,你可能也在使用内置的setHidden方法)。 - Jack
显示剩余5条评论

39

最好的方法如下面博客所述:http://ali-reynolds.com/2013/06/29/hide-cells-in-static-table-view/

像平常在interface builder中设计静态表视图一样完成所有潜在隐藏单元格的设计。但是,有一件事情必须为每个想要隐藏的单元格做 - 检查单元格的“剪裁子视图”属性,否则当您尝试隐藏单元格(通过缩小其高度 - 更多细节可见于后面)时,单元格的内容不会消失。

所以,你有一个开关在一个单元格中,这个开关应该隐藏和显示一些静态单元格。将它与一个IBAction连接,然后在其中执行以下操作:

[self.tableView beginUpdates];
[self.tableView endUpdates];

这可以为出现和消失的单元格提供漂亮的动画效果。现在实现以下表视图委托方法:

- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 1 && indexPath.row == 1) { // This is the cell to hide - change as you need
    // Show or hide cell
        if (self.mySwitch.on) {
            return 44; // Show the cell - adjust the height as you need
        } else {
            return 0; // Hide the cell
        }
   }
   return 44;
}

就是这样。转动开关,单元格就会隐现,并带有一个漂亮流畅的动画效果。


更新:您还需要将单元格标签设置为与您的单元格隐藏/显示一起隐藏/显示,否则标签会弄乱。 - Mohamed Saleh
1
请注意,如果静态单元格内容中的任何UIView具有任何约束条件指向cell.content,则如果这些约束条件对于新的单元格高度无效,则可能会出现运行时错误。 - Pedro Borges
1
我知道这个技巧适用于动态内容。对于静态内容也非常好用。而且非常简单。 - Ants
2
这个确实有效,但为了解决一个缺点:如果要隐藏/显示的行包含日期选择器,则仅执行[self.tableView beginUpdates]; [self.tableView endUpdates]; 仍会在隐藏时留下故障。为了消除这个故障,您应该调用 [self.tableView reloadRowsAtIndexPaths:@[indexPathOfTargetRow] withRowAnimation:UITableViewRowAnimationNone]; 另外要注意的是,在这里行动画有些“被禁用”。 - Eddie Deng
那个博客链接现在是不是已经死了,指向一个“可疑”的网站?还是只有我这样? - Red

34

我的解决方案与Gareth类似,虽然我做了一些不同的事情。

具体如下:

1. 隐藏单元格

没有直接隐藏单元格的方法。 UITableViewController是提供静态单元格的数据源,目前还没有办法告诉它“不要提供单元格x”。 因此,我们必须提供自己的数据源,该数据源委托UITableViewController以获取静态单元格。

最简单的方法是子类化UITableViewController,并覆盖需要在隐藏单元格时表现不同的所有方法

在最简单的情况下(单节表格,所有单元格具有相同的高度),可以按照以下方式进行:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section    
{
    return [super tableView:tableView numberOfRowsInSection:section] - numberOfCellsHidden;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Recalculate indexPath based on hidden cells
    indexPath = [self offsetIndexPath:indexPath];

    return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}

- (NSIndexPath*)offsetIndexPath:(NSIndexPath*)indexPath
{
    int offsetSection = indexPath.section; // Also offset section if you intend to hide whole sections
    int numberOfCellsHiddenAbove = ... // Calculate how many cells are hidden above the given indexPath.row
    int offsetRow = indexPath.row + numberOfCellsHiddenAbove;

    return [NSIndexPath indexPathForRow:offsetRow inSection:offsetSection];
}
如果您的表格有多个部分或单元格高度不同,则需要覆盖更多的方法。相同的原则适用于此处:在委托给 super 之前,您需要偏移 indexPath、section 和 row。
还要记住,对于像didSelectRowAtIndexPath:这样的方法,indexPath 参数会因状态(即隐藏的单元格数)而导致同一单元格产生不同的值。因此,通常最好偏移任何 indexPath 参数并使用这些值进行操作。
2.动画改变
如Gareth所述,如果使用reloadSections:withRowAnimation:方法来动画显示更改,将会出现严重的故障。
我发现,如果立即调用reloadData:,动画效果会大大改善(只剩下轻微的故障)。动画后,表格会正确显示。
所以我的做法是:
- (void)changeState
{
     // Change state so cells are hidden/unhidden
     ...

    // Reload all sections
    NSIndexSet* reloadSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfSectionsInTableView:tableView])];

    [tableView reloadSections:reloadSet withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView reloadData];
}

你解决了小的动画故障吗?对我来说,单元格之间的分隔线无法正确地进行动画效果,我宁愿不使用动画,也不要有故障。不过解决方案很棒! - Maximilian Körner
我的问题是,使用静态单元格时,numberOfRowsInSection: 仅在表格第一次加载时调用。当我调用 [self.tableView reloadData] 时,numberOfRowsInSection: 永远不会再次被调用。只有 cellForRowAtIndexPath: 被调用。我错过了什么吗? - Roman

15
  1. 在设计师中,为您想要隐藏的单元格创建一个出口。例如,您想要隐藏“cellOne”,所以在viewDidLoad()中执行以下操作:

cellOneOutlet.hidden = true

现在覆盖下面的方法,检查哪个单元格状态被隐藏并返回高度0。这是在Swift中隐藏静态tableView中任何单元格的众多方法之一。

override func tableView(tableView: UITableView, heightForRowAtIndexPathindexPath: NSIndexPath) -> CGFloat 
{

let tableViewCell = super.tableView(tableView,cellForRowAtIndexPath: indexPath)

        if tableViewCell.hidden == true
        {
            return 0
        }
        else{
             return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
        }

}

到目前为止最佳解决方案! - derdida
在进行更改后添加tableView.reloadData(),就完美了。这样节省了我很多修改解决方案的时间!谢谢。 - Shayan C
1
Swift 4不允许我使用let tableViewCell = super.tableView(tableView,cellForRowAtIndexPath: indexPath)。我猜它被let tableViewCell = tableView.cellForRow(at: indexPath as IndexPath)替换了。 - Clifton Labrum
由于我在UITableViewController上使用静态单元格,因此我没有任何UITableView委托方法。为了让它调用heightForRow,我还需要其他方法吗? - Clifton Labrum
@CliftonLabrum 不,您只能覆盖此方法。 - Alexander Ershov

12
原来,你可以在静态的UITableView中隐藏和显示单元格,并带有动画效果。而且它并不难实现。
要点如下:
  1. 使用tableView:heightForRowAtIndexPath:根据某些状态动态指定单元格高度。
  2. 当状态改变时,通过调用tableView.beginUpdates();tableView.endUpdates() 来使单元格动画显示/隐藏。
  3. 不要在tableView:heightForRowAtIndexPath:中调用tableView.cellForRowAtIndexPath:。使用缓存的indexPaths来区分单元格。
  4. 不要隐藏单元格。在Xcode中设置"Clip Subviews"属性。
  5. 使用自定义单元格(而不是Plain等)可获得漂亮的隐藏动画。此外,正确处理自动布局以适应单元格高度==0的情况。
演示项目 演示项目视频 更多信息请查看我的博客(俄语)

1
这个点对我来说非常重要:“不要隐藏单元格。在Xcode中设置“剪辑子视图”属性。” - balkoth

11
我提出了一种替代方案,它实际上可以隐藏部分而不删除它们。我尝试了@henning77的方法,但当我更改静态UITableView的部分数量时,我经常遇到问题。这种方法对我非常有效,但我主要是想隐藏部分而不是行。我成功地动态删除了一些行,但这样会变得很乱,所以我尝试将需要显示或隐藏的内容分组到各个部分中。以下是我如何隐藏部分的示例:
首先,我声明了一个NSMutableArray属性。
@property (nonatomic, strong) NSMutableArray *hiddenSections;

在viewDidLoad方法中(或者在您查询完数据之后),您可以将想要隐藏的部分添加到数组中。
- (void)viewDidLoad
{
    hiddenSections = [NSMutableArray new];

    if(some piece of data is empty){
        // Add index of section that should be hidden
        [self.hiddenSections addObject:[NSNumber numberWithInt:1]];
    }

    ... add as many sections to the array as needed

    [self.tableView reloadData];
}

然后实现以下TableView委托方法。
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
        return nil;
    }

    return [super tableView:tableView titleForHeaderInSection:section];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:indexPath.section]]){
        return 0;
    }

    return [super tableView:tableView heightForRowAtIndexPath:[self offsetIndexPath:indexPath]];
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:indexPath.section]]){
        [cell setHidden:YES];
    }
}

将头部和尾部的高度设置为1,以隐藏部分,因为无法将高度设置为0。这会导致额外的2像素空间,但是我们可以通过调整下一个可见标题的高度来弥补这个问题。

-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 
{
    CGFloat height = [super tableView:tableView heightForHeaderInSection:section];

    if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
        height = 1; // Can't be zero
    }
    else if([self tableView:tableView titleForHeaderInSection:section] == nil){ // Only adjust if title is nil
        // Adjust height for previous hidden sections
        CGFloat adjust = 0;

        for(int i = (section - 1); i >= 0; i--){
            if([self.hiddenSections containsObject:[NSNumber numberWithInt:i]]){
                adjust = adjust + 2;
            }
            else {
                break;
            }
        }

        if(adjust > 0)
        {                
            if(height == -1){
                height = self.tableView.sectionHeaderHeight;
            }

            height = height - adjust;

            if(height < 1){
                height = 1;
            }
        }
    }

    return height;
}

-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section 
{   
    if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
        return 1;
    }
    return [super tableView:tableView heightForFooterInSection:section];
}

如果您确实有需要隐藏的特定行,则可以调整numberOfRowsInSection和在cellForRowAtIndexPath中返回哪些行。在这个例子中,我有一个部分有三行,其中任何三行都可能为空并需要被删除。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger rows = [super tableView:tableView numberOfRowsInSection:section];

    if(self.organization != nil){
        if(section == 5){ // Contact
            if([self.organization objectForKey:@"Phone"] == [NSNull null]){     
                rows--;
            }

            if([self.organization objectForKey:@"Email"] == [NSNull null]){     
                rows--;
            }

            if([self.organization objectForKey:@"City"] == [NSNull null]){     
                rows--;
            }
        }
    }

    return rows;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{    
    return [super tableView:tableView cellForRowAtIndexPath:[self offsetIndexPath:indexPath]];
}

使用此offsetIndexPath来计算需要有条件删除行的indexPath。如果仅隐藏部分,则不需要。请保留HTML标签。
- (NSIndexPath *)offsetIndexPath:(NSIndexPath*)indexPath
{
    int row = indexPath.row;

    if(self.organization != nil){
        if(indexPath.section == 5){
            // Adjust row to return based on which rows before are hidden
            if(indexPath.row == 0 && [self.organization objectForKey:@"Phone"] == [NSNull null] && [self.organization objectForKey:@"Email"] != [NSNull null]){     
                row++;
            }
            else if(indexPath.row == 0 && [self.organization objectForKey:@"Phone"] == [NSNull null] && [self.organization objectForKey:@"Address"] != [NSNull null]){     
                row = row + 2;
            }
            else if(indexPath.row == 1 && [self.organization objectForKey:@"Phone"] != [NSNull null] && [self.organization objectForKey:@"Email"] == [NSNull null]){     
                row++;
            }
            else if(indexPath.row == 1 && [self.organization objectForKey:@"Phone"] == [NSNull null] && [self.organization objectForKey:@"Email"] != [NSNull null]){     
                row++;
            }
        }
    }

    NSIndexPath *offsetPath = [NSIndexPath indexPathForRow:row inSection:indexPath.section];

    return offsetPath;
}

有很多覆盖方法,但我喜欢这种方法的可重用性。设置hiddenSections数组,添加到其中,它将隐藏正确的部分。隐藏行有点棘手,但是可以实现。如果我们使用分组的UITableView,我们不能仅将要隐藏的行的高度设置为0,因为边框将无法正确绘制。


1
我可能会使用NSMutableSet来代替hiddenSections。因为它更快,而你大多数时候只是在测试成员资格。 - pixelfreak
很好的回答,奥斯汀。我投了这个答案,并将其用作将来项目的参考。pixelfreak也提出了一个很好的观点,即对于“hiddenSections”使用NSMutableSet,尽管我理解你的答案更多地是概念性的,而不是挑剔哪种类型的数据结构应该使用。 - BigSauce

9

根据Justas的回答,但适用于Swift 4:

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    let cell = super.tableView(tableView, cellForRowAt: indexPath)

    if cell == self.cellYouWantToHide {
        return 0
    }

    return super.tableView(tableView, heightForRowAt: indexPath)
}

1
如果您已经改变了行高度,而又需要更新正在显示的单元格,那么您可能需要使用tableView.reloadRows(at:, with:)来更新这些单元格。 - Frost-Lee

8
是的,这绝对是可能的,尽管我目前也在努力解决同样的问题。我成功地使单元格隐藏并且一切正常,但我目前无法使它平稳地动画化。以下是我的发现:
我根据第一部分中第一行的开/关开关的状态隐藏行。如果开关打开,则在同一部分下方有1行,否则有2个不同的行。
当切换开关时,我有一个选择器,并设置一个变量来指示我处于哪种状态。然后我调用:
[[self tableView] reloadData];

我重写了 tableView:willDisplayCell:forRowAtIndexPath: 方法,如果该单元格需要被隐藏,我做了如下操作:
[cell setHidden:YES];

那会隐藏单元格及其内容,但不会移除它所占据的空间。
要移除该空间,请覆盖 tableView:heightForRowAtIndexPath: 函数并返回应隐藏行的高度为 0。
还需要覆盖 tableView:numberOfRowsInSection: 并返回该部分中的行数。在这里您必须做一些奇怪的事情,以便如果表格是分组样式,则圆角出现在正确的单元格上。在我的静态表格中,该部分有完整的单元格集,因此有包含选项的第一个单元格,然后是 1 个用于 ON 状态选项和 2 个用于 OFF 状态选项的单元格,总共 4 个单元格。当选项为 ON 时,我必须返回 4,这包括隐藏的选项,以便最后显示的选项具有圆形框。当选项关闭时,最后两个选项不会显示,因此我返回 2。这一切都感觉很笨重。如果描述不太清楚,很抱歉。只是为了说明设置,这是 IB 中表格部分的构造:

  • 行 0:带有 ON/OFF 开关的选项
  • 行 1:当选项为 ON 时显示
  • 行 2:当选项为 OFF 时显示
  • 行 3:当选项为 OFF 时显示

因此,当选项为 ON 时,表格报告的两行是:

  • 行 0:带有 ON/OFF 开关的选项
  • 行 1:当选项为 ON 时显示

当选项关闭时,表格报告的四行是:

  • 行 0:带有 ON/OFF 开关的选项
  • 行 1:当选项为 ON 时显示
  • 行 2:当选项为 OFF 时显示
  • 行 3:当选项为 OFF 时显示

这种方法之所以不正确,原因有几个,这只是我迄今为止的实验结果,请让我知道是否有更好的方法。到目前为止,我观察到的问题有:

  • 告诉表格行数与底层数据中的行数不同感觉不对。

  • 我似乎无法动画显示更改。我尝试使用 tableView:reloadSections:withRowAnimation: 而不是 reloadData,但结果似乎不合理,我仍在努力让它工作。目前似乎发生的情况是 tableView 没有更新正确的行,因此一个应该显示的行仍然隐藏,而第一行下留下了一个空白。我认为这可能与底层数据的第一点有关。

希望有人能够提出替代方法或者如何扩展动画,但也许这会让您有所启示。很抱歉没有超链接到函数,我加入了它们,但由于我是一个相对较新的用户,它们被垃圾邮件过滤器拒绝了。


我非常确定在隐藏单元格后,您应该再次调用[[self tableView] reloadData]; - Shmidt
抱歉重新提出这个问题,但是您是如何对UITableViewController进行子类化并与静态单元格一起使用的?Xcode不允许我这样做。 - Dany Joumaa
Nessup,您不需要子类化UITableView。在Storyboard中选择您的TableViewController,然后单击TableView。现在,您可以在动态/静态单元格之间进行选择。 - Shmidt
1
我找到了如何对静态单元格进行动画显示/隐藏的解决方案。只需在创建静态单元格的UITableViewController子类中调用return [super tableView:tableView cellForRowAtIndexPath:indexPath];即可。索引路径是静态索引,而非动态的... - k06a

7

好的,在尝试一些方法后,我有一个非常规的答案。我使用“isHidden”或“hidden”变量来检查此单元格是否应该隐藏。

  1. 为您的视图控制器创建一个IBOutlet。 @IBOutlet weak var myCell: UITableViewCell!

  2. 在您的自定义函数中更新myCell,例如您可以在viewDidLoad中添加它:

override func viewDidLoad() { super.viewDidLoad() self.myCell.isHidden = true }

  1. 在您的委托方法中:

override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { let cell = super.tableView(tableView, cellForRowAt: indexPath) guard !cell.isHidden else { return 0 } return super.tableView(tableView, heightForRowAt: indexPath) }

这将简化您的委托方法中的逻辑,您只需要专注于业务需求即可。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接