Ios UITableView,具有动态高度单元的手风琴式动画
我正试图在一个带有动态高度单元的UITableView中创建一个类似手风琴的动画 首次尝试使用重载行 问题似乎是重新加载行会导致单元格高度的即时更新,因此尽管tableview会设置动画,但单元格本身不会设置其高度的动画。这导致其他细胞有时重叠并干扰扩展的细胞内容 第二次尝试使用BeginUpdate/EndUpdate 这是我找到的最接近细胞按需要膨胀的地方。然而,当折叠时,TeTeVIEW立即消失,使标题挂在中间,直到崩溃。 目标 我尝试实现的效果与尝试2中展开时的效果完全相同,但在折叠时效果也相反(手风琴/展示风格) 注意:如果可能的话,我还希望继续使用StackView在UITextView上设置Ios UITableView,具有动态高度单元的手风琴式动画,ios,swift,uitableview,uistackview,Ios,Swift,Uitableview,Uistackview,我正试图在一个带有动态高度单元的UITableView中创建一个类似手风琴的动画 首次尝试使用重载行 问题似乎是重新加载行会导致单元格高度的即时更新,因此尽管tableview会设置动画,但单元格本身不会设置其高度的动画。这导致其他细胞有时重叠并干扰扩展的细胞内容 第二次尝试使用BeginUpdate/EndUpdate 这是我找到的最接近细胞按需要膨胀的地方。然而,当折叠时,TeTeVIEW立即消失,使标题挂在中间,直到崩溃。 目标 我尝试实现的效果与尝试2中展开时的效果完全相同,但在折叠
hidden
属性,因为我正在处理的实际项目涉及其他几个子视图,这些子视图的动画应该与UITextView类似
我使用的是XCode 11 beta 7
首次尝试使用重载行的代码:
表格控制器
细胞
故事板
使用BeginUpdate/EndUpdate尝试2的代码
与尝试1完全相同的代码,不同的是,…didSelectRowAt…
替换为:
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
tableView.beginUpdates()
if let selectedRow = selectedRow,
let prevCell = tableView.cellForRow(at: selectedRow) as? MyTableViewCell {
prevCell.configureExpansion(false)
}
let selectedCell = tableView.cellForRow(at: indexPath) as? MyTableViewCell
if selectedRow == indexPath {
selectedCell?.configureExpansion(false)
selectedRow = nil
} else {
selectedCell?.configureExpansion(true)
selectedRow = indexPath
}
tableView.endUpdates()
}
我最终在BeginUpdate/EndUpdate的基础上解决了这个问题。接下来我将进一步研究这个问题和解决方案 问题 如原问题所述,扩展阶段工作正常。这样做的原因是:
isHidden
属性设置为false
时,包含的堆栈视图会调整大小,并为单元格提供新的固有内容大小endUpdates
时,单元格将自动展开,因为内部大小已更改且tableView.rowHeight=.automaticDimension
。动画将根据需要逐渐显示新内容isHidden
属性设置为true
时,包含堆栈的视图会隐藏视图,并将单元格调整为新的固有内容大小李>
endUpdates
时,动画将通过立即删除UITextView开始。想一想,这是意料之中的,因为这与扩张期间发生的情况相反。然而,它也打破了预期的动画效果,留下可见元素“挂”在中间,而不是逐渐隐藏UITExtVIEW当行收缩。< /LI>
解决办法
要获得所需的隐藏效果,当单元格收缩时,UITextView应在整个动画期间保持可见。这包括几个步骤:
步骤1:覆盖heightForRow
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
// If a cell is collapsing, force it to its original height stored in collapsingRow?
// instead of using the intrinsic size and .automaticDimension
if indexPath == collapsingRow?.indexPath {
return collapsingRow!.height
} else {
return UITableView.automaticDimension
}
}
当单元收缩时,UITextView现在将保持可见,但由于自动布局,UITextView会立即被剪裁,因为它定位到单元高度
步骤2:在UIStackView上创建高度约束,并使其在单元格收缩时具有优先权
2.1:在Interface builder中的UIStackView上添加了高度约束
2.2:在MyTableViewCell中添加了heightConstraint
作为strong(不弱,这很重要)出口
2.3在MyTableViewCell的awakeFromNib
中,设置heightConstraint.isActive=false
以保持默认行为
2.4在interface builder中:确保UIStackView底部约束的优先级设置低于高度约束的优先级。即底部约束的999
,高度约束的1000
。如果不这样做,则会在折叠阶段导致约束冲突
步骤3:折叠时,激活高度约束
并将其设置为UIStackView的当前固有大小。这样可以在单元格高度降低时保持UITextView的内容可见,但也可以根据需要剪裁内容,从而产生“隐藏”效果
步骤4:使用CATTransaction.setCompletionBlock完成动画时重置状态
这些步骤结合在一起
你得到的正是我所期望的结果。您正在更改单元格内容,然后进行手风琴演奏。这正是我们所看到的。好吧,这不是我所期望的。我期待一个类似的动画:啊,但这是一个零高度折叠没有内容变化。所以你没有理由期待。这很容易实现,但不是你正在做的。好吧,jqueryui.com/accordion/#collapsable的问题是标题和内容是不同的对象-你可以使用不同的标签来解决这个问题。展开时,1)创建另一个标签,或2)已经有一个要翻转的标签visible@matt这不是一个很有帮助的评论。我想,如果你知道解决方案,这很容易实现,但如果我有解决方案,我不会问
class MyTableViewCell: UITableViewCell {
@IBOutlet weak var title: UILabel!
@IBOutlet weak var textView: UITextView!
func configure(expanded: Bool, post: Post) {
title.text = post.title
textView.text = post.content
configureExpansion(expanded)
}
func configureExpansion(_ expanded: Bool) {
self.textView.isHidden = !expanded
self.contentView.backgroundColor = expanded ?
.red : .systemBackground
}
}
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
tableView.beginUpdates()
if let selectedRow = selectedRow,
let prevCell = tableView.cellForRow(at: selectedRow) as? MyTableViewCell {
prevCell.configureExpansion(false)
}
let selectedCell = tableView.cellForRow(at: indexPath) as? MyTableViewCell
if selectedRow == indexPath {
selectedCell?.configureExpansion(false)
selectedRow = nil
} else {
selectedCell?.configureExpansion(true)
selectedRow = indexPath
}
tableView.endUpdates()
}
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
// If a cell is collapsing, force it to its original height stored in collapsingRow?
// instead of using the intrinsic size and .automaticDimension
if indexPath == collapsingRow?.indexPath {
return collapsingRow!.height
} else {
return UITableView.automaticDimension
}
}
if let expandedRow = expandedRow,
let prevCell = tableView.cellForRow(at: expandedRow.indexPath) as? MyTableViewCell {
prevCell.heightConstraint.constant = prevCell.stackView.frame.height
prevCell.heightConstraint.isActive = true
collapsingRow = expandedRow
}
class MyTableViewController: UITableViewController {
var expandedRow: (indexPath: IndexPath, height: CGFloat)? = nil
var collapsingRow: (indexPath: IndexPath, height: CGFloat)? = nil
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.backgroundColor = .darkGray
self.tableView.rowHeight = UITableView.automaticDimension
self.tableView.estimatedRowHeight = 200
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(
withIdentifier: "Cell", for: indexPath) as? MyTableViewCell
else { fatalError() }
let post = posts[indexPath.row]
let isExpanded = expandedRow?.indexPath == indexPath
cell.configure(expanded: isExpanded, post: post)
return cell
}
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == collapsingRow?.indexPath {
return collapsingRow!.height
} else {
return UITableView.automaticDimension
}
}
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
guard let tappedCell = tableView.cellForRow(at: indexPath) as? MyTableViewCell
else { return }
CATransaction.begin()
tableView.beginUpdates()
if let expandedRow = expandedRow,
let prevCell = tableView.cellForRow(at: expandedRow.indexPath)
as? MyTableViewCell {
prevCell.heightConstraint.constant = prevCell.stackView.frame.height
prevCell.heightConstraint.isActive = true
CATransaction.setCompletionBlock {
if let cell = tableView.cellForRow(at: expandedRow.indexPath)
as? MyTableViewCell {
cell.configureExpansion(false)
cell.heightConstraint.isActive = false
}
self.collapsingRow = nil
}
collapsingRow = expandedRow
}
if expandedRow?.indexPath == indexPath {
collapsingRow = expandedRow
expandedRow = nil
} else {
tappedCell.configureExpansion(true)
expandedRow = (indexPath: indexPath, height: tappedCell.frame.height)
}
tableView.endUpdates()
CATransaction.commit()
}
}