【问题标题】:UITableViewCell expand on clickUITableViewCell 点击展开
【发布时间】:2011-06-05 20:10:32
【问题描述】:

假设我们有一个自定义 UITableViewCell

因此,每当我单击单元格上的自定义按钮时.. 它应该会扩大到一定程度(您可以说 40 高度...),当我再次单击同一个自定义按钮时,它应该折叠到以前的高度。

请开发者指导我..我怎样才能完成这项任务

【问题讨论】:

  • 注意:如果您希望扩展区域包含新的可单击单元格(行)(而不仅仅是为单击的单元格扩展空间),请参阅gcamp's answer。它从一组关闭的sections开始;单击部分标题以打开该部分,显示其单元格。

标签: iphone objective-c uitableview uibutton


【解决方案1】:

考虑到它是完全正确的,我不会在这里说任何与接受的答案相矛盾的东西。但是,我将更详细地介绍如何实现这一点。如果您不想通读所有这些并且对在工作项目中使用源代码更感兴趣,我已经上传了example project to GitHub

基本思想是在方法-tableView: heightForRowAtIndexPath: 中有一个条件来确定当前单元格是否应该展开。这将通过在-tableView: didSelectRowAtIndexPath: 中调用表格的开始/结束更新来触发。在此示例中,我将展示如何制作一个允许一次展开一个单元格的表格视图。

您需要做的第一件事是声明对NSIndexPath 对象的引用。你可以随心所欲地这样做,但我建议使用这样的属性声明:

@property (strong, nonatomic) NSIndexPath *expandedIndexPath;

注意:您不需要在 viewDidLoad 或任何其他类似方法中创建此索引路径。索引最初为 nil 的事实仅意味着该表最初不会有扩展行。如果您希望表格从您选择的行开始展开,您可以在 viewDidLoad 方法中添加类似的内容:

NSInteger row = 1;
NSInteger section = 2;
self.expandedIndexPath = [NSIndexPath indexPathForRow:row inSection:section];

下一步是转到 UITableViewDelegate 方法 -tableView: didSelectRowAtIndexPath: 添加逻辑以根据用户选择更改扩展单元格索引。这里的想法是检查刚刚选择的索引路径与存储在expandedIndexPath 变量中的索引路径。如果两者匹配,那么我们知道用户正在尝试取消选择展开的单元格,在这种情况下,我们将变量设置为 nil。否则,我们将expandedIndexPath 变量设置为刚刚选择的索引。这一切都是在调用 beginUpdates/endUpdates 之间完成的,以允许表格视图自动处理过渡动画。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView beginUpdates]; // tell the table you're about to start making changes

    // If the index path of the currently expanded cell is the same as the index that
    // has just been tapped set the expanded index to nil so that there aren't any
    // expanded cells, otherwise, set the expanded index to the index that has just
    // been selected.
    if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
        self.expandedIndexPath = nil;
    } else {
        self.expandedIndexPath = indexPath;
    }

    [tableView endUpdates]; // tell the table you're done making your changes
}

那么最后一步是在另一个UITableViewDelegate方法-tableView: heightForRowAtIndexPath:中。在您为表确定需要更新的每个索引路径触发一次beginUpdates 后,将调用此方法。您可以在此处将expandedIndexPath 与当前正在重新评估的索引路径进行比较。

如果两个索引路径相同,那么这就是你希望展开的单元格,否则它的高度应该是正常的。我使用了值 100 和 44,但您可以使用任何适合您需要的值。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Compares the index path for the current cell to the index path stored in the expanded
    // index path variable. If the two match, return a height of 100 points, otherwise return
    // a height of 44 points.
    if ([indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
        return 100.0; // Expanded height
    }
    return 44.0; // Normal height
}

【讨论】:

  • 您能否解释一下在beginUpdatesendUpdates 之间没有任何内容的新版本中表格视图如何处理动画?谢谢
【解决方案2】:

实现 heightForRowAtIndexPath 以计算正确的高度。然后在按钮的代码中,强制表格使用 beginUpdates 和 endUpdates 重新评估每个单元格的高度:

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

tableview 单元格高度的更改将自动使用 heightForRowAtIndexPath 计算,并且更改也将被动画化。

事实上,您甚至可以在didSelectRowAtIndexPath 中选择单元格来执行此操作,而不是您的单元格上的按钮。

【讨论】:

    【解决方案3】:

    我没有使用 [tableView beginUpdates][tableView endUpdates] ,而是在 didSelectRowAtIndexPath 方法中使用 [tableView reloadRowsAtIndexPath:... withRowAnimation:...] 方法。

    我更喜欢这个,因为当我扩展我的UITableViewCell 时,当我使用开始和结束更新方法时,我遇到了一些应该显示的元素问题。另一点是您可以在一些动画之间进行选择,例如:上、下、左、右...

    【讨论】:

    • 关于更好地控制动画的深刻见解!
    【解决方案4】:

    我为此创建了一个开源库。您只需在代码中实现折叠和展开委托,!您还可以执行任何绘图和动画。查看this

    【讨论】:

    • 如果您对 tableview 单元格进行子类化而不是使用 viewWithTag,则可以改进项目
    • HVTableView 有一个新的更新。它现在也在 cocoapods 上提供。
    【解决方案5】:

    我已经制作了一个可重复使用的组件,它完全可以满足您的要求。它非常易于使用,并且有一个演示项目。

    GCRetractableSectionController 在 GitHub 上。

    【讨论】:

    • 这正是我所需要的。 UITableviewCell 中的 UITableview。完美的。谢谢。 :)
    • 对我来说,这也是完美的解决方案 - 我想知道如何让扩展区域包含额外的可点击单元格行,而不仅仅是单个单元格。
    【解决方案6】:

    这是 Mick 的答案,但适用于 Swift 4。(IndexPath 替换了带有空 IndexPath 的 NSIndexPath,因为 nil 会使 Swift 崩溃。此外,您可以使用 == 比较 IndexPath 的两个实例)

    声明扩展索引路径属性。

    var expandedIndexPath = IndexPath()
    

    可选的 viewDidLoad 部分。

    expandedIndexPath = IndexPath(row: 1, section: 2)
    

    然后是 didSelectRow 部分。

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.beginUpdates()
    
        if indexPath == expandedIndexPath {
            expandedIndexPath = IndexPath()
        } else {
            expandedIndexPath = indexPath
        }
    
        tableView.endUpdates()
    }
    

    然后是 heightForRow 部分。

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if indexPath == expandedIndexPath {
            return 100
        }
    
        return 44
    }
    

    【讨论】:

      【解决方案7】:

      我使用了 Gcamp 的源代码并制作了自己的版本。

      1) 在 loadView 方法中初始化一个可变数组,您将在其中保存部分的展开或非展开状态。将扩展状态保存在单独的数组中至关重要,该数组在表格视图滚动时不会被破坏(例如,如果将其存储在 headerView 中,它将被重绘并忘记它是否被扩展的天气)。就我而言,它是 _sectionStatuses 数组。

      - (void)loadView
      {
           // At the beginning all sections are expanded
          _sectionStates = [NSMutableArray arrayWithCapacity:self.tableView.numberOfSections];
          for (int i = 0; i < self.tableView.numberOfSections; i++) {
              _sectionStates[i] = [NSNumber numberWithBool:YES];
          }
      }
      

      2) 为带有展开按钮的部分创建自定义 headerView。使用委托模式将 headerView 中的按钮的操作委托给 TableViewController。您可以在 Gcamp 的源代码中找到合适的图像。

      3) 创建一个动作来删除或添加行。这里 _foldersArray 是我的结构,它包含所有数据。我的部分的 headerView - MCExpandableAccountHeaderView 知道它自己的部分编号 - 当我为每个部分创建标题视图时,我将它转移到那里。将其转移到此方法至关重要,因为您必须知道现在扩展或拉伸了哪个部分。

      - (void)expandClicked:(MCAccountHeaderView *)sender
      {
      MCExpandableAccountHeaderView *expandableAccountHeaderView = (MCExpandableAccountHeaderView*)sender;
      
      // Finding a section, where a button was tapped
      NSInteger section = expandableAccountHeaderView.section;
      
      // Number of rows, that must be in a section when it is expanded
      NSUInteger contentCount = [_foldersArray[section - 1][@"folders"] count];
      
      // Change a saved status of a section
      BOOL expanded = [_sectionStates[section] boolValue];
      expanded = ! expanded;
      expandableAccountHeaderView.expanded = expanded;
      _sectionStates[section] = [NSNumber numberWithBool:expanded];
      
      // Animation in a table
      [self.tableView beginUpdates];
      
      NSMutableArray* modifiedIndexPaths = [[NSMutableArray alloc] init];
      for (NSUInteger i = 0; i < contentCount; i++) {
          NSIndexPath* indexPath = [NSIndexPath indexPathForRow:i inSection:section];
          [modifiedIndexPaths addObject:indexPath];
      }
      
      if (expandableAccountHeaderView.expanded) [self.tableView insertRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
      else [self.tableView deleteRowsAtIndexPaths:modifiedIndexPaths withRowAnimation:UITableViewRowAnimationFade];
      
      [self.tableView endUpdates];
      
      // Scroll to the top of current expanded section
      if (expandableAccountHeaderView.expanded) [self.tableView scrollToRowAtIndexPath:INDEX_PATH(0, section) atScrollPosition:UITableViewScrollPositionTop animated:YES];
      }
      

      4) 根据是否展开,在一个部分中返回正确的数字或​​行也很重要。

      - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
      {
           BOOL expanded = [_sectionStates[section] boolValue];
      
           return expanded ? [_foldersArray[section - 1][@"folders"] count] : 0;   
      }
      

      【讨论】:

        【解决方案8】:
        initialize iSelectedIndex = -1; and declare
        UITableView *urTableView;
        
        - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
        
        return 10;    //Section count
        
        }
        
        - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
        {
        
        return 3; //row count
        
        }
        
        - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
        {
        
        static NSString *CellIdentifier = @"Cell";
        
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        
        if(cell == nil)
        {
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
        
        }
        
        [cell.textLabel setText:[NSString stringWithFormat:@"sec:%d,row:%d",indexPath.section,indexPath.row]];
        
        return cell;
        
        }
        
        
        - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
        
        // adding a label with the tap gesture to the header in each section
        
        headerLabel = [[UILabel alloc]init]; 
        
        headerLabel.tag = section;
        
        headerLabel.userInteractionEnabled = YES;
        
        headerLabel.backgroundColor = [UIColor greenColor];
        
        headerLabel.text = [NSString stringWithFormat:@"Header No.%d",section];
        
        headerLabel.frame = CGRectMake(0, 0, tableView.tableHeaderView.frame.size.width, tableView.tableHeaderView.frame.size.height);
        
        UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(gestureTapped:)];
        
        [headerLabel addGestureRecognizer:tapGesture];
        
        return headerLabel;
        
        }
        
        - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
        
        return 50.0; //adjust the height as you need
        
        }
        
        - (void)gestureTapped:(UITapGestureRecognizer *)sender{
        
        UIView *theSuperview = self.view; // whatever view contains 
        
        CGPoint touchPointInSuperview = [sender locationInView:theSuperview];
        
        UIView *touchedView = [theSuperview hitTest:touchPointInSuperview withEvent:nil];
        
        if([touchedView isKindOfClass:[UILabel class]])
        {
        
            if (iSelectedIndex != touchedView.tag) { //if new header is selected , need to expand
        
                iSelectedIndex = touchedView.tag;
        
            }else{   // if the header is already expanded , need to collapse
        
                iSelectedIndex = -1;
        
            }
        
            [urTableView beginUpdates];
        
            [urTableView endUpdates];
        
        }
        
        }
        
        - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        
        // Show or hide cell
        
        float height = 0.0;
        
        if (indexPath.section == iSelectedIndex) {
        
            height = 44.0; // Show the cell - adjust the height as you need
        
        }
        
        return height;
        
        }
        

        【讨论】:

        • 只是不要发布很多代码。试着解释一下。 :)
        • 完美运行!非常感谢。数小时试图找到这个解决方案。
        【解决方案9】:

        对我来说,它可以使用:

        1. 关于 UITableViewDelegate

          func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

              print("Did select row: \(indexPath.row).")
          
              tableView.beginUpdates()
              tableView.endUpdates()
          }
          
        2. 关于可选择/可扩展的 UITableViewCell

          覆盖 func setSelected(_ selected: Bool, 动画: Bool) { super.setSelected(selected, animated: 动画)

             configStyle(selected)
          }
          
        3. 重要! tableView.rowHeight.automatic 并且 UITableViewCell 是启用自动高度计算的约束,即它的高度约束被明确定义为顶部/底部约束或添加的高度约束或使用标签固有内容大小。

        【讨论】:

          【解决方案10】:

          要添加到 0x7fffffff 的 答案,我发现我需要在 didSelectRowAtIndexPath 内的 if 语句中添加一个额外条件 - 因此:

          - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath 
          {
          
             [tableView beginUpdates];
          
             if (self.expandedIndexPath && [indexPath compare:self.expandedIndexPath] == NSOrderedSame) {
                 self.expandedIndexPath = nil;
             } else {
                 self.expandedIndexPath = indexPath;
             }
          
             [tableView endUpdates];
          
          }
          

          【讨论】:

            【解决方案11】:

            按照medium article 了解如何通过点击按钮来扩展单元格并为特定标签设置numbersOfLine,我能够使用

            执行动画
            tableView.beginUpdates()
            tableView.performBatchUpdates({
              cell.description.numberOfLines = !expanded ? 0 : 3
            }, completion: nil)
            tableView.endUpdates()
            

            注意performBatchUpdates 仅适用于 iOS 11⬆️

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-11-06
              • 1970-01-01
              • 2018-12-11
              • 1970-01-01
              • 2020-11-10
              • 2012-10-26
              • 1970-01-01
              相关资源
              最近更新 更多