I'm trying to create an accordion-like animation inside a UITableView with cells of dynamic height.
First attempt using reloadRows
The issue seems to be that reloadRows results in an instant cell height update, so although the tableview animates, the cell itself doesn't animate its height. This results in other cells sometimes overlapping and interfering with the expanded cells content.
Second attempt using beginUpdates/endUpdates
This is the closest I've got where the cell expands as desired. However when collapsing, the TextView disappears immediately leaving the title hanging in the middle until collapsed.
Goal
What I try to accomplish is the exact same effect as when expanding in attempt 2, but also in reverse when collapsing (accordion/reveal-style).
Note: if possible I would also like to keep using a StackView for setting the hidden
property on the UITextView, as the actual project I'm working on involves several other subviews that should animate similar to the UITextView.
I'm using XCode 11 beta 7.
Code for first attempt using reloadRows:
struct Post {
var id: String
var title: String
var content: String
}
let posts = [
Post(id: "1", title: "Post 1", content: "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est"),
Post(id: "2", title: "Post 2", content: "Lorem ipsum dolor"),
Post(id: "3", title: "Post 3", content: "Lorem ipsum dolor"),
Post(id: "4", title: "Post 4", content: "Lorem ipsum dolor"),
Post(id: "5", title: "Post 5", content: "Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda."),
Post(id: "6", title: "Post 6", content: "Lorem ipsum dolor")
]
UITableViewController
class MyTableViewController: UITableViewController {
var selectedRow: IndexPath? = 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 = selectedRow == indexPath
cell.configure(expanded: isExpanded, post: post)
return cell
}
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
var reloadPaths: [IndexPath] = []
if selectedRow != nil {
reloadPaths.append(selectedRow!)
}
if selectedRow == indexPath {
selectedRow = nil
} else {
reloadPaths.append(indexPath)
selectedRow = indexPath
}
tableView.reloadRows(at: reloadPaths, with: .automatic)
}
}
Cell
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
}
}
Storyboard
Code for attempt 2 using beginUpdates/endUpdates
Exact same code as attempt 1, except ...didSelectRowAt...
is replaced with:
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()
}