Skip to content

Instantly share code, notes, and snippets.

@christianselig
Created November 30, 2020 18:29
Show Gist options
  • Save christianselig/063dbce4690ec227e009b6e6e286cae2 to your computer and use it in GitHub Desktop.
Save christianselig/063dbce4690ec227e009b6e6e286cae2 to your computer and use it in GitHub Desktop.
Trying to insert a table view cell at the top, without offsetting the current scroll position and causing a jump. Insert occurs when you tap one of the star accessory views, and seems due to the two different types of cells used in the table.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView(frame: CGRect.zero, style: .plain)
var totalAdded = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: "SubtitleCell")
tableView.dataSource = self
tableView.delegate = self
view.addSubview(tableView)
view.addConstraint(NSLayoutConstraint(item: tableView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 0.0))
view.addConstraint(NSLayoutConstraint(item: tableView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1.0, constant: 0.0))
view.addConstraint(NSLayoutConstraint(item: tableView, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1.0, constant: 0.0))
view.addConstraint(NSLayoutConstraint(item: tableView, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1.0, constant: 0.0))
}
func numberOfSections(in tableView: UITableView) -> Int {
return UILocalizedIndexedCollation.current().sectionTitles.count + 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 3
} else if section == 1 {
return totalAdded
} else {
return 8
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubtitleCell", for: indexPath)
cell.textLabel?.text = "Grapes \(indexPath.row)"
cell.detailTextLabel?.text = "I love grapes!"
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = "Oranges \(indexPath.row)"
cell.accessoryView = createStarAccessoryButton()
return cell
}
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 {
return 60.0
} else {
return 44.0
}
}
func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return ["!", "*"] + UILocalizedIndexedCollation.current().sectionTitles
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Cool Stuff"
} else if section == 1 {
return "Favorites"
} else {
return UILocalizedIndexedCollation.current().sectionTitles[section - 2]
}
}
func createStarAccessoryButton() -> UIButton {
let button = UIButton(type: .system)
button.tintColor = .lightGray
button.setImage(UIImage(systemName: "star.fill")!, for: .normal)
button.sizeToFit()
button.addTarget(self, action: #selector(starButtonTapped(sender:)), for: .touchUpInside)
return button
}
@objc func starButtonTapped(sender: UIButton) {
let buttonPosition = sender.convert(CGPoint.zero, to: tableView)
guard let indexPath = tableView.indexPathForRow(at: buttonPosition) else { return }
let height = tableView.cellForRow(at: indexPath)!.bounds.height
print("Height: \(height)")
let oldContentOffset = tableView.contentOffset.y
totalAdded += 1
tableView.performBatchUpdates {
self.tableView.insertRows(at: [IndexPath(row: 0, section: 1)], with: .none)
} completion: { (didComplete) in
self.tableView.setNeedsLayout()
self.tableView.layoutIfNeeded()
self.tableView.contentOffset.y = oldContentOffset + height
}
}
}
class SubtitleTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
@christianselig
Copy link
Author

Even weirder, it only occurs on the first insert, all the subsequent inserts don’t cause jumps!

@TheCoordinator
Copy link

TheCoordinator commented Nov 30, 2020

@christianselig This one won't jump and will maintain contentOffset which is an old trick I used to do and it is ridiculously ugly but might be doing the job for you.

Replace the whole performBatchUpdates with this.

UIView.setAnimationsEnabled(false)
tableView.beginUpdates()

tableView.insertRows(at: [IndexPath(row: 0, section: 1)], with: .automatic)

tableView.endUpdates()
UIView.setAnimationsEnabled(true)

var contentOffset = self.tableView.contentOffset
contentOffset.y = oldContentOffset + height

tableView.setContentOffset(contentOffset, animated: false)

@christianselig
Copy link
Author

@TheCoordinator Ooo, thank you, I always appreciate more tools in the ol' belt!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment