选择后,您可以为 UITableViewCell 上的高度变化设置动画吗?
Posted
技术标签:
【中文标题】选择后,您可以为 UITableViewCell 上的高度变化设置动画吗?【英文标题】:Can you animate a height change on a UITableViewCell when selected? 【发布时间】:2009-01-20 03:17:52 【问题描述】:我在我的 iPhone 应用程序中使用UITableView
,并且我有一个属于某个组的人员列表。我希望这样当用户单击特定的人(从而选择单元格)时,单元格的高度会增加以显示几个 UI 控件来编辑该人的属性。
这可能吗?
【问题讨论】:
【参考方案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]
会立即发生高度变化,而不是动画。以上是关于选择后,您可以为 UITableViewCell 上的高度变化设置动画吗?的主要内容,如果未能解决你的问题,请参考以下文章
当您向上或向下滚动时,选择一个 UITableViewCell 会选择同一位置的其他单元格