UITableViewCell扩展点击

时间:2011-01-08 18:09:47

标签: iphone objective-c uitableview uibutton

让我们说我们有一个自定义的UITableViewCell

所以每当我点击单元格上的自定义按钮时,它应该扩展到某种程度(你可以说更多40个高度......)当我再次点击相同的自定义按钮时,它应该折叠到之前的高度。 / p>

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

11 个答案:

答案 0 :(得分:94)

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

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

您需要做的第一件事是声明对NSIndexPath对象的引用。您可以随意执行此操作,但我建议使用如下属性声明:

@property (strong, nonatomic) NSIndexPath *expandedIndexPath;

注意:您无需在viewDidLoad或任何其他类似方法中创建此索引路径。索引最初为零的事实只意味着表最初不会有扩展行。如果您希望表格从一行展开的选择开始,您可以在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
}

答案 1 :(得分:67)

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

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

对tableview单元格高度的更改将自动使用heightForRowAtIndexPath计算,并且更改也将设置为动画。

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

答案 2 :(得分:9)

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

enter image description here

答案 3 :(得分:8)

我已经制作了一个可重用的组件,可以完全按照你所说的去做。它非常易于使用,还有一个演示项目。

GCRetractableSectionController在GitHub上。

答案 4 :(得分:8)

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

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

答案 5 :(得分:3)

这是Mick的答案,但是对于Swift 4.(IndexPath取代了NSIndexPath,它带有一个空的IndexPath,因为nil会使Swift崩溃。另外,你可以使用==来比较两个IndexPath实例)< / p>

声明expandedIndexPath属性。

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
}

答案 6 :(得分:2)

我使用了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;   
}

答案 7 :(得分:1)

对我来说,它可以使用:

  1. 在UITableViewDelegate上

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

    select units.*, 
           case when 1 = 
               row_number() over (
                   partition by org_id 
                   order by case origin when 'current' then 1 when 'history' then 2 else 3 end, 
                            unit_valid_from ) then 1 else 0 end as is_correct_version
      from units
    
  2. 在可选/可扩展的UITableViewCell上

    重写功能setSelected(_选择:布尔,动画:布尔){         super.setSelected(已选择,动画:动画)

        print("Did select row: \(indexPath.row).")
    
        tableView.beginUpdates()
        tableView.endUpdates()
    }
    
  3. 重要! configStyle(selected) } tableView.rowHeight,UITableViewCell是用于启用自动高度计算的约束,即,它的高度约束已明确定义,例如对顶部/底部的约束或添加的高度约束或使用了标签固有内容大小。

答案 8 :(得分:0)

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 :(得分:0)

要添加到 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];

}

答案 10 :(得分:0)

medium article关于如何基于按钮的点击扩展单元格并为特定标签设置numbersOfLine之后,我能够使用

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

通知performBatchUpdates仅在iOS 11中可用⬆️