I have a TableViewController which displays to-do list items. In the controller I have made a button which when pressed creates a new TableViewCell at the bottom which has a UITextView along with other elements.
Till now this is what I have managed to do -
- Create a new cell upon button tap
- Make the newly created cell's text view first responder
However, from what I have observed everything is working fine except when the last cell in the table is not visible, i.e., it is below the frame. In that case the cell gets created but is not made the first responder or some other cell's text view gets the cursor.
See the output here - https://drive.google.com/file/d/1mRN8MEO5HBJ3ICUiRE0Yc4ib8tp62MYc/view?usp=sharing
Here is the code -
import UIKit
class TableViewController: UITableViewController, InboxCellDelegate {
var cell = InboxCell()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.leftBarButtonItem = self.editButtonItem
tableView.register(UINib(nibName: "InboxCell", bundle: nil), forCellReuseIdentifier: "InboxCell")
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 50
}
@IBAction func inboxAddPressed(_ sender: UIBarButtonItem) {
addRowToEnd()
}
func addRowToEnd() {
Task.tasks.append("")
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: Task.tasks.count - 1, section: 0)], with: .automatic)
tableView.endUpdates()
cell.inboxTaskTextView.becomeFirstResponder()
}
func didChangeText(text: String?, cell: InboxCell) {
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Task.tasks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell = tableView.dequeueReusableCell(withIdentifier: "InboxCell", for: indexPath) as! InboxCell
// Configure the cell...
cell.delegate = self
cell.inboxTaskTextView.text = Task.tasks[indexPath.row]
return cell
}
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
tableView.beginUpdates()
Task.tasks.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
tableView.endUpdates()
}
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
I have tried to scroll to the bottom of the table first and then making the newly created cell first responder but that didn't work. In that case only the very first cell created becomes the first responder while the subsequent cells are created but the cursor remains in the very first cell.
Here is the block of code I used for scrolling before cell.inboxTaskTextView.becomeFirstResponder() -
DispatchQueue.main.async {
self.tableView.reloadData()
let indexPath = IndexPath(row: Task.tasks.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
Edit -
After having tried for a while this is the closest I have got to a solution.
After Task.tasks.append("") I have added the following code which scrolls down the view to the bottom -
if tableView.contentSize.height >= tableView.frame.size.height {
DispatchQueue.main.async {
self.tableView.reloadData()
let indexPath = IndexPath(row: Task.tasks.count - 1, section: 0)
self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
}
In this case the newly created cell becomes first responder but only momentarily. The keyboard doesn't even appear fully before it gets dismissed automatically in a flash. This happens only for cells that are created below the fold - i.e. when the table view has to scroll down and then create a new cell.