【问题标题】:Can you animate a height change on a UITableViewCell when selected?选择后,您可以为 UITableViewCell 上的高度变化设置动画吗?
【发布时间】:2025-12-31 19:55:01
【问题描述】:

我在我的 iPhone 应用程序中使用UITableView,并且我有一个属于某个组的人员列表。我希望这样当用户单击特定的人(从而选择单元格)时,单元格的高度会增加以显示几个 UI 控件来编辑该人的属性。

这可能吗?

【问题讨论】:

    标签: ios cocoa-touch uitableview


    【解决方案1】:

    我找到了一个非常简单的解决方案,作为我正在研究的UITableView 的副作用.....

    将单元格高度存储在一个变量中,该变量通常通过tableView: heightForRowAtIndexPath: 报告原始高度,然后当您想要动画高度变化时,只需更改变量的值并调用它...

    [tableView beginUpdates];
    [tableView endUpdates];
    

    您会发现它并没有完全重新加载,但足以让UITableView 知道它必须重新绘制单元格,获取单元格的新高度值......猜猜怎么着?它为您带来变化。甜蜜。

    我的博客上有更详细的解释和完整的代码示例...Animate UITableView Cell Height Change

    【讨论】:

    • 这太棒了。但是有什么办法可以控制表格的动画速度呢?
    • 这行得通,但如果我让一个单元格变大,让我们说从 44 到 74,然后再把它变小到 44,分隔线的行为就很奇怪。有人可以证实这一点吗?
    • 这是一个奇怪的解决方案,但它也是 Apple 在 WWDC 2010 “Mastering Table View”会议中推荐的。我将提交一份关于将此添加到文档中的错误报告,因为我刚刚花了大约 2 个小时进行研究。
    • 我已经尝试过这个解决方案,但它有时只能工作,当你按下一个单元格时,有 50% 的概率工作。有人有同样的错误吗?是因为iOS7?
    • 现在官方文档里也写了:You can also use this method followed by the endUpdates method to animate the change in the row heights without reloading the cell.developer.apple.com/library/ios/documentation/UIKit/Reference/…
    【解决方案2】:

    我喜欢 Simon Lee 的回答。我实际上并没有尝试这种方法,但它看起来会改变列表中所有单元格的大小。我希望只改变被窃听的单元格。我有点像西蒙那样做,但只有一点点不同。这将在选中单元格时更改其外观。它确实有动画效果。只是另一种方法。

    创建一个 int 来保存当前选定单元格索引的值:

    int currentSelection;
    

    然后:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
        int row = [indexPath row];
        selectedNumber = row;
        [tableView beginUpdates];
        [tableView endUpdates];
    }
    

    然后:

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    
        if ([indexPath row] == currentSelection) {
            return  80;
        }
        else return 40;
    
    
    }
    

    我相信您可以在 tableView:cellForRowAtIndexPath: 中进行类似的更改,以更改单元格的类型,甚至为单元格加载 xib 文件。

    像这样,currentSelection 将从 0 开始。如果您不希望列表的第一个单元格(位于索引 0 处)默认被选中,则需要进行调整。

    【讨论】:

    • 检查附在我帖子上的代码,我就是这样做的,选中时将单元格的高度加倍。 :)
    • “我实际上并没有尝试过这种方法,但它看起来会改变列表中所有单元格的大小”——那么你看起来并不难。
    • 当前选择已经存储在tableView.indexPathForSelectedRow中。
    【解决方案3】:

    添加一个属性来跟踪选定的单元格

    @property (nonatomic) int currentSelection;
    

    将其设置为(例如)viewDidLoad 中的标记值,以确保 UITableView 从“正常”位置开始

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
    
        //sentinel
        self.currentSelection = -1;
    }
    

    heightForRowAtIndexPath 中,您可以为所选单元格设置所需的高度

    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
        int rowHeight;
        if ([indexPath row] == self.currentSelection) {
            rowHeight = self.newCellHeight;
        } else rowHeight = 57.0f;
        return rowHeight;
    }
    

    didSelectRowAtIndexPath 中保存当前选择并保存动态高度(如果需要)

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
            // do things with your cell here
    
            // set selection
            self.currentSelection = indexPath.row;
            // save height for full text label
            self.newCellHeight = cell.titleLbl.frame.size.height + cell.descriptionLbl.frame.size.height + 10;
    
            // animate
            [tableView beginUpdates];
            [tableView endUpdates];
        }
    }
    

    didDeselectRowAtIndexPath 中将选择索引设置回标记值并将单元格设置为正常形式

    - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {       
            // do things with your cell here
    
            // sentinel
            self.currentSelection = -1;
    
            // animate
            [tableView beginUpdates];
            [tableView endUpdates];
        }
    }
    

    【讨论】:

    • 谢谢谢谢谢谢!我添加了一些代码来使单元格可以切换。我在下面添加了代码。
    【解决方案4】:

    现在推荐的电话是:而不是beginUpdates()/endUpdates()

    tableView.performBatchUpdates(nil, completion: nil)
    

    关于 beginUpdates/endUpdates,Apple 表示:“尽可能使用 performBatchUpdates(_:completion:) 方法而不是这个方法。”

    见:https://developer.apple.com/documentation/uikit/uitableview/1614908-beginupdates

    【讨论】:

      【解决方案5】:

      reloadData 不好,因为没有动画...

      这是我目前正在尝试的:

      NSArray* paths = [NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:0]];
      [self.tableView beginUpdates];
      [self.tableView insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
      [self.tableView deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
      [self.tableView endUpdates];
      

      它几乎可以正常工作。几乎。我正在增加单元格的高度,有时在替换单元格时表格视图中会出现一些“打嗝”,好像表格视图中的某些滚动位置正在被保留,新单元格(这是第一个单元格在表格中) 的偏移量过高,滚动视图会反弹以重新定位它。

      【讨论】:

      • 我个人发现使用这种方法但使用 UITableViewRowAnimationNone 可以提供更平滑但仍不完美的结果。
      【解决方案6】:

      我不知道连续调用 beginUpdates/endUpdates 的这些东西是什么,你可以使用-[UITableView reloadRowsAtIndexPaths:withAnimation:]Here is an example project.

      【讨论】:

      • 非常好,这不会在我的自动布局单元格中拉伸我的文本视图。但它确实必须对动画进行闪烁,因为在更新单元格大小时,无动画选项看起来会出现故障。
      【解决方案7】:

      我用reloadRowsAtIndexPaths 解决了。

      我将所选单元格的 indexPath 保存在 didSelectRowAtIndexPath 中,并在最后调用 reloadRowsAtIndexPaths(您可以发送 NSMutableArray 以获取要重新加载的元素列表)。

      heightForRowAtIndexPath 中,您可以检查 indexPath 是否在 expandIndexPath 单元格的列表中并发送高度。

      您可以查看这个基本示例: https://github.com/ferminhg/iOS-Examples/tree/master/iOS-UITableView-Cell-Height-Change/celdascambiadetam 这是一个简单的解决方案。

      如果对你有帮助,我会添加一种代码

      - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
          return 20;
      }
      
      -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath: (NSIndexPath*)indexPath
      {
          if ([indexPath isEqual:_expandIndexPath])
              return 80;
      
          return 40;
      }
      
      - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
          static NSString *CellIdentifier = @"Celda";
      
          UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
      
          [cell.textLabel setText:@"wopwop"];
      
          return cell;
      }
      
      #pragma mark -
      #pragma mark Tableview Delegate Methods
      
      - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
          NSMutableArray *modifiedRows = [NSMutableArray array];
          // Deselect cell
          [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
          _expandIndexPath = indexPath;
          [modifiedRows addObject:indexPath];
      
          // This will animate updating the row sizes
          [tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
      }
      

      【讨论】:

        【解决方案8】:

        Swift 4 及更高版本

        将以下代码添加到 tableview 的 didselect 行委托方法中

        tableView.beginUpdates()
        tableView.setNeedsLayout()
        tableView.endUpdates()
        

        【讨论】:

          【解决方案9】:

          试试这个是为了扩展索引行:

          @property (nonatomic) NSIndexPath *expandIndexPath;
          - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
          {
          if ([indexPath isEqual:self.expandedIndexPath])
              return 100;
          
          return 44;
          }
          
          - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
          {
          NSMutableArray *modifiedRows = [NSMutableArray array];
          if ([indexPath isEqual:self.expandIndexPath]) {
              [modifiedRows addObject:self.expandIndexPath];
              self.expandIndexPath = nil;
          } else {
              if (self.expandedIndexPath)
                  [modifiedRows addObject:self.expandIndexPath];
          
              self.expandIndexPath = indexPath;
              [modifiedRows addObject:indexPath];
          }
          
          // This will animate updating the row sizes
          [tableView reloadRowsAtIndexPaths:modifiedRows withRowAnimation:UITableViewRowAnimationAutomatic];
          
          // Preserve the deselection animation (if desired)
          [tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
          [tableView deselectRowAtIndexPath:indexPath animated:YES];
          }
          
          - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
          {
              UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ViewControllerCellReuseIdentifier];
              cell.textLabel.text = [NSString stringWithFormat:@"I'm cell %ld:%ld", (long)indexPath.section, (long)indexPath.row];
          
          return cell;
          }
          

          【讨论】:

            【解决方案10】:
            BOOL flag;
            
            - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
            {
                flag = !flag;
                [tableView beginUpdates];
                [tableView reloadRowsAtIndexPaths:@[indexPath] 
                                 withRowAnimation:UITableViewRowAnimationAutomatic];
                [tableView endUpdates];
            }
            
            - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
            {
                return YES == flag ? 20 : 40;
            }
            

            【讨论】:

              【解决方案11】:

              只是给像我这样的人在自定义单元格上搜索添加“更多详细信息”的注释。

              [tableView beginUpdates];
              [tableView endUpdates];
              

              做得很好,但不要忘记“裁剪”单元格视图。 从 Interface Builder 中选择您的 Cell -> Content View -> 从 Property Inspector 中选择 "Clip subview"

              【讨论】:

                【解决方案12】:

                这是 Swift 3 的 Simons 答案的较短版本。还允许切换单元格的选择

                var cellIsSelected: IndexPath?
                
                
                  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
                    cellIsSelected = cellIsSelected == indexPath ? nil : indexPath
                    tableView.beginUpdates()
                    tableView.endUpdates()
                  }
                
                
                  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
                    if cellIsSelected == indexPath {
                      return 250
                    }
                    return 65
                  }
                

                【讨论】:

                  【解决方案13】:

                  Simon Lee 答案的 Swift 版本。

                  // MARK: - Variables 
                    var isCcBccSelected = false // To toggle Bcc.
                  
                  
                  
                      // MARK: UITableViewDelegate
                  func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
                  
                      // Hide the Bcc Text Field , until CC gets focused in didSelectRowAtIndexPath()
                      if self.cellTypes[indexPath.row] == CellType.Bcc {
                          if (isCcBccSelected) {
                              return 44
                          } else {
                              return 0
                          }
                      }
                  
                      return 44.0
                  }
                  

                  然后在 didSelectRowAtIndexPath() 中

                    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
                      self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
                  
                      // To Get the Focus of CC, so that we can expand Bcc
                      if self.cellTypes[indexPath.row] == CellType.Cc {
                  
                          if let cell = tableView.cellForRowAtIndexPath(indexPath) as? RecipientTableViewCell {
                  
                              if cell.tag == 1 {
                                  cell.recipientTypeLabel.text = "Cc:"
                                  cell.recipientTextField.userInteractionEnabled = true
                                  cell.recipientTextField.becomeFirstResponder()
                  
                                  isCcBccSelected = true
                  
                                  tableView.beginUpdates()
                                  tableView.endUpdates()
                              }
                          }
                      }
                  }
                  

                  【讨论】:

                    【解决方案14】:

                    是的,这是可能的。

                    UITableView 有一个委托方法didSelectRowAtIndexPath

                    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
                    {
                        [UIView animateWithDuration:.6
                                              delay:0
                             usingSpringWithDamping:UIViewAnimationOptionBeginFromCurrentState
                              initialSpringVelocity:0
                                            options:UIViewAnimationOptionBeginFromCurrentState animations:^{
                    
                                                cellindex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
                                                NSArray* indexArray = [NSArray arrayWithObjects:indexPath, nil];
                                                [violatedTableView beginUpdates];
                                                [violatedTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
                                                [violatedTableView endUpdates];
                                            }
                                         completion:^(BOOL finished) {
                        }];
                    }
                    

                    但在您的情况下,如果用户滚动并选择不同的单元格,那么您需要让最后一个选定的单元格缩小和展开当前选定的单元格 reloadRowsAtIndexPaths: 调用 heightForRowAtIndexPath: 以便相应地处理。

                    【讨论】:

                      【解决方案15】:

                      这是我的自定义UITableView 子类的代码,它在表格单元格处展开UITextView,无需重新加载(并且丢失键盘焦点):

                      - (void)textViewDidChange:(UITextView *)textView {
                          CGFloat textHeight = [textView sizeThatFits:CGSizeMake(self.width, MAXFLOAT)].height;
                          // Check, if text height changed
                          if (self.previousTextHeight != textHeight && self.previousTextHeight > 0) {
                              [self beginUpdates];
                      
                              // Calculate difference in height
                              CGFloat difference = textHeight - self.previousTextHeight;
                      
                              // Update currently editing cell's height
                              CGRect editingCellFrame = self.editingCell.frame;
                              editingCellFrame.size.height += difference;
                              self.editingCell.frame = editingCellFrame;
                      
                              // Update UITableView contentSize
                              self.contentSize = CGSizeMake(self.contentSize.width, self.contentSize.height + difference);
                      
                              // Scroll to bottom if cell is at the end of the table
                              if (self.editingNoteInEndOfTable) {
                                  self.contentOffset = CGPointMake(self.contentOffset.x, self.contentOffset.y + difference);
                              } else {
                                  // Update all next to editing cells
                                  NSInteger editingCellIndex = [self.visibleCells indexOfObject:self.editingCell];
                                  for (NSInteger i = editingCellIndex; i < self.visibleCells.count; i++) {
                                      UITableViewCell *cell = self.visibleCells[i];
                                      CGRect cellFrame = cell.frame;
                                      cellFrame.origin.y += difference;
                                      cell.frame = cellFrame;
                                  }
                              }
                              [self endUpdates];
                          }
                          self.previousTextHeight = textHeight;
                      }
                      

                      【讨论】:

                        【解决方案16】:

                        我使用了@Joy 的绝妙答案,它与 ios 8.4 和 XCode 7.1.1 完美配合。

                        如果您想让您的单元格可切换,我将 -tableViewDidSelect 更改为以下内容:

                        -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
                        //This is the bit I changed, so that if tapped once on the cell, 
                        //cell is expanded. If tapped again on the same cell, 
                        //cell is collapsed. 
                            if (self.currentSelection==indexPath.row) {
                                self.currentSelection = -1;
                            }else{
                                self.currentSelection = indexPath.row;
                            }
                                // animate
                                [tableView beginUpdates];
                                [tableView endUpdates];
                        
                        }
                        

                        我希望这些对您有所帮助。

                        【讨论】:

                          【解决方案17】:

                          在 iOS 7 及更高版本后检查此方法。

                          - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath{
                              return UITableViewAutomaticDimension;
                          }
                          

                          iOS 8 对此进行了改进。我们可以将其设置为表格视图本身的属性。

                          【讨论】:

                            【解决方案18】:

                            Simon Lee's answer 的 Swift 版本:

                            tableView.beginUpdates()
                            tableView.endUpdates()
                            

                            请记住,您应该修改高度属性之前 endUpdates()

                            【讨论】:

                              【解决方案19】:

                              输入 -

                              tableView.beginUpdates() tableView.endUpdates() 这些函数不会调用

                              func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}

                              但是,如果你这样做, tableView.reloadRows(at: [selectedIndexPath! as IndexPath], with: .none)

                              它将调用 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {} 这个函数。

                              【讨论】:

                                【解决方案20】:

                                我刚刚通过一点 hack 解决了这个问题:

                                static int s_CellHeight = 30;
                                static int s_CellHeightEditing = 60;
                                
                                - (void)onTimer {
                                    cellHeight++;
                                    [tableView reloadData];
                                    if (cellHeight < s_CellHeightEditing)
                                        heightAnimationTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001 target:self selector:@selector(onTimer) userInfo:nil repeats:NO] retain];
                                }
                                
                                - (CGFloat)tableView:(UITableView *)_tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
                                {
                                        if (isInEdit) {
                                            return cellHeight;
                                        }
                                        cellHeight = s_CellHeight;
                                        return s_CellHeight;
                                }
                                

                                当我需要扩展单元格高度时,我设置isInEdit = YES 并调用方法[self onTimer],它会为单元格生长设置动画,直到达到 s_CellHeightEditing 值:-)

                                【讨论】:

                                • 在模拟器中效果很好,但在 iPhone 中硬件比较滞后。有 0.05 的计时器延迟和 5 个单位的 cellHeight 增加,这要好得多,但与 CoreAnimation 完全不同
                                • 确认,发帖前..下一次。
                                【解决方案21】:

                                获取所选行的索引路径。重新加载表。在 UITableViewDelegate 的 heightForRowAtIndexPath 方法中,将选中的行的高度设置为不同的高度,其他的返回正常的行高

                                【讨论】:

                                • -1,不起作用。调用 [table reloadData] 会立即发生高度变化,而不是动画。