选中后,可以在UITableViewCell上设置高度变化的动画吗?


393

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

这可能吗?

Answers:


879

我发现了一个非常简单的解决方案,这是UITableView我正在研究的副作用.....

将像元高度存储在通常通过来报告原始高度的变量中tableView: heightForRowAtIndexPath:,然后在要对高度变化进行动画处理时,只需更改变量的值并调用它即可。

[tableView beginUpdates];
[tableView endUpdates];

您会发现它并不会完全重载,但是足以UITableView让它知道必须重绘单元格,获取单元格的新高度值....然后猜猜是什么?为您动画化变化。甜。

我的博客上有更详细的解释和完整的代码示例。Animate UITableView单元格高度变化


6
太好了 但是,有什么方法可以控制表格的动画速度?
乔什·卡汉

7
这行得通,但是如果我将一个单元格增大,可以说是从44到74,然后再使其减小到44,则分隔线表现得很奇怪。有人可以确认吗?
plaetzchen

46
这是一个奇怪的解决方案,但这也是Apple在WWDC 2010“ Mastering Table View”会话中的建议。我将提交一份关于将其添加到文档中的错误报告,因为我仅花了大约2个小时的时间进行研究。
bpapa

5
我已经尝试过这种解决方案,并且它有时仅在按下单元格时起作用的可能性为50%。任何人都有相同的错误吗?是因为iOS7吗?
琼·卡多纳

7
现在,它也被写入了正式文件: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/...
雅罗斯拉夫·

63

我喜欢西蒙·李的回答。我实际上并没有尝试该方法,但看起来它将更改列表中所有单元格的大小。我希望只更改被窃听的单元格。我有点像西蒙(Simon),但有一点点不同。选定单元格后,它将改变其外观。它确实具有动画效果。只是另一种方式。

创建一个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)默认处于选中状态,则需要进行调整。


2
检查附加在我帖子中的代码,我确实做到了这一点,当选中时,单元格的高度加倍。:)
西蒙·李

8
“我实际上并没有尝试该方法,但是看起来它会改变列表中所有单元格的大小”-然后,您看起来并不难。
杰米

13
当前选择已存储在tableView.indexPathForSelectedRow中。
尼克

22

添加属性以跟踪所选单元格

@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];
    }
}

谢谢你,谢谢你,谢谢你!我添加了一些代码以使单元可切换。我在下面添加了代码。
Septronic 2015年

14

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];

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


我个人发现使用此方法但结合使用UITableViewRowAnimationNone可以提供更平滑但仍不是完美的结果。
罗恩·斯雷布罗

11

我不知道有关连续调用beginUpdates / endUpdates的所有这些内容,您可以使用-[UITableView reloadRowsAtIndexPaths:withAnimation:]这是一个示例项目


太好了,这不会在自动布局单元格中拉伸文本视图。但这确实要使动画闪烁,因为在更新像元大小时,“无动画”选项看起来会出现故障。
h3dkandi

10

我解决了 reloadRowsAtIndexPaths

我保存在didSelectRowAtIndexPath所选单元格的indexPath中并调用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];
}


3

尝试这样做是为了扩展按索引的行:

@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;
}

3
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;
}

2

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

[tableView beginUpdates];
[tableView endUpdates];

做得很棒,但别忘了“裁剪”单元格视图。从Interface Builder中选择您的单元格-> Content View->从Property Inspector中选择“ 剪辑子视图


2

这是Simons对Swift 3的简短回答。也允许切换单元格的选择

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
  }

2

Swift 4及以上

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

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

1

迅捷版的Simon Lee的答案。

// 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()
            }
        }
    }
}

1

是的,有可能。

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:以便进行相应处理。


0

这是我的自定义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;
}

0

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

}

希望这些对您有所帮助。


0

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

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

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



0

输入-

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

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

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

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


-1

我只是通过一点技巧解决了这个问题:

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的计时器延迟,并且cellHeight增加了5个单位,效果要好得多,但是却没有CoreAnimation
Dzamir

1
确认,然后再发布。
ajay_nasa 2015年

-2

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


2
-1,不起作用。调用[table reloadData]结果会立即发生高度变化,而不是进行动画处理。
Mark Amery
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.