Table view cell is remain unselected in editing mood - ios

I want even numbered rows of tableview to be selected when table is first loaded. I have set the tableview editing style to "Multiple Selection During Editing" from storyboard. I have tried two ways to do this, first approach is like below
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.setEditing(true, animated: true)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell?.textLabel?.text = data[indexPath.row]
if (indexPath.row % 2 == 0){
cell?.isSelected = true
}else {
cell?.isSelected = false
}
return cell!
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
but it does not make the row selected. table looks like
second way i tried is to call self.tableView.selectRow(at: index path, animated: true, scrollPosition: UITableViewScrollPosition.none) whith removing additional logic in cellForRow method used in first approach. this approach seems to work.
Can anyone tell me why first approach is not working and weather my second approach is appropriate or not. Thanks :)

Check if you are doing deselectRowAtIndexPath in didSelectRowAtIndexPath method.

Related

UITableView Cell Does Not Become First Responder When Outside of Current View

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.
Try to keep it simple first. Put a breakpoint or print("indexPath.row = \(indexPath.row)") at the beginning of cellForRow UITableViewDataSource method that you implemented already.
Add the new task and see if your cellForRow is being called for the indexPath corresponding to your new cell.
If not - you may have to scroll the tableView up at least 44points or whatever is needed to reach the area where the new cell should already be displayed. If you don't do that - the cell might not be created yet, and cell most probably refers to the last cell in the table view (or it could also be referring to a cell in the pool if some logic is not implemented correctly). So the new cell must be in the visible area before making its UITextField or UITextView become first responder.
If you know that cell is already visible - better to get an reference to it via
let index = IndexPath(row: rowForNewCell, section: 0)
let cell = self.table.cellForRow(at: index)
Finally call:
cell.inboxTaskTextView.becomeFirstResponder()

UITableView: how stop selection while swipe the table view cell

I have swipe to delete code here and it my custom TableViewCell I have implemented setSelected method like below ..
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
// tableView.allowsSelectionDuringEditing = false
if tableView.indexPathForSelectedRow != nil, tableView.indexPathForSelectedRow == indexPath {
return UITableViewCell.EditingStyle.none
}
return UITableViewCell.EditingStyle.delete
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
//some code here
}
The logic will do tableview expand collapse based on selection ..but the problem here is if I swipe to delete setSelected also triggers.. not sure how to prevent that any help would be appreciated..
Try this in cellForRow
let cell = tableView.dequeueReusableCell(withIdentifier: "cell_identifier", for: indexPath)
cell.selectionStyle = UITableViewCell.SelectionStyle.none
//or this based on swift version
cell.selectionStyle = .none
return cell
I'm not sure what the appearance you are going for but this will produce the same effect.
If swipe is across the whole screen it will trigger the delete without button press.
If half swipe you can present buttons for user to choose options.
Half swipe (Shows options):
Full swipe (Triggers delete button):
Try adding these two tableView delegate methods for a swipe to delete
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Determine which rows should be editable
return true
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath:
IndexPath) -> [UITableViewRowAction]? {
let button1 = UITableViewRowAction(style: .default, title: "Delete") {
action, indexPath in
print("delete pressed")
// Consider alert to confirm that it was intentionally deleted
}
button1.backgroundColor = UIColor.red
// Create any buttons you want
return [button1]
}
I tried it with and without your tableView method and it seemed to work fine both ways but I don't think your method is necessary if you choose this route
Hope this helps

How to save an indexPath in a user default and how to have a cell in a tableview pre-selected as it is loaded? (xcode 8, swift 3.0)

So I have been looking all over the internet for an answer to this but to no avail. Basically, I have code to have cells in a tableview have a check mark when selected, which works fine. However, when selected, I want the indexPath to be saved in a user default so that the next time the user views it, that particular cell that she had selected before can be pre-selected:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
UserDefaults.standard.set(indexPath as IndexPath, forKey: "OnHomeShow")
}
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
}
}
The saving in the user default didn't work! Anyone able to help? Furthermore, how do I get it so that I can have a pre-selected cell? I used this code here:
override func viewDidAppear(_ animated: Bool) {
if let x = UserDefaults.standard.object(forKey: "OnHomeShow") as? IndexPath
{
//I want to pre-select the cell set by the user-default
}
else
{
//I want to pre-select the first cell
}
}
But I have no idea what code to put to select a cell programmatically! Anyone know how to help? Thanks!

Set Checkmark on view did appear using will display cell not working correctly

Here is my code for the WillDisplayRow function:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let monetaryCell = tableView.cellForRow(at: [1,0])
//configure you cell here.
if AppState.sharedInstance.filterPaymentMonetaryIsOn {
monetaryCell?.accessoryType = .checkmark
return monetaryCell!
} else {
monetaryCell?.accessoryType = .none
return monetaryCell!
}
}
And in my DidSelectRow:
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
let monetaryCell = tableView.cellForRow(at: [1,0])
tableView.deselectRow(at: indexPath as IndexPath, animated: true)
if indexPath == [0,0] {
print(indexPath)
} else if indexPath == [1,0] {
if AppState.sharedInstance.filterPaymentMonetaryIsOn == true {
monetaryCell!.accessoryType = .none
AppState.sharedInstance.filterPaymentMonetaryIsOn = false
} else {
monetaryCell?.accessoryType = .checkmark
AppState.sharedInstance.filterPaymentMonetaryIsOn = true
}
Can someone help me out as to why this isnt working? Also, I have alot more cells and when selected each one has the same properties as above. What I'm looking for is the checkmark to be displayed when I reload the view or close and reopen the view. I hope this makes sense. I'm sorry if this is a duplicate, I looked for a few hours but couldn't find a similar situation.
Thank you in advance.
Denis Angell
Move your check mark display functionality from cellForRowAtIndexPath method to willDisplayCell method of UITableViewDelegate. willDisplayCell method works in such a way that whenever the cell about to appear on screen willDisplayCell method calls and desired functionality executes as expected.

Table view marking more than one cell Swift 3

I have a VERY annoying problem.
I have a tableView with more or less 50 cells, displaying some options to which I can select the ones I want. I read in Apple's documentation that by default the cells are reused when they are not displayed. With this, if I select the first cell, every 6 cells 1 is marked, that is, if I select the first 6 cells, ALL cells in the table are marked!
My table view allows multiple selections. The selection is being made like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
How do I solve this? I know you have such a "prepareForReuse ()" for the subclass, would that be the solution? If so, can you give me an example of how you would do it?
here is code it may help you
var arr_selectedindePath = NSMutableArray()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
if arr_selectedindePath .contains(indexPath) {
arr_selectedindePath .remove(indexPath)
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.none
}
else
{
arr_selectedindePath .add(indexPath)
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCellAccessoryType.checkmark
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell")!
if arr_selectedindePath .contains(indexPath) {
cell.accessoryType = .checkmark
}
else
{
cell.accessoryType = .none
}
}
You will need to update your data model accordingly so that when cellForRowAtIndexPath is called it displays cells with updated content.
If you don't use a datamodel you will need to store the indexPath's in a mutable array and make a check whether the current indexPath is marked or not.
Hope this helps.
In your custom cell
1.Create a delegate
protocol CellDelegate {
func didTapOnButton(_ cell:Cell)
}
2. Declare delegate
var delegate:CellDelegate?
3.Override this method
override func prepareForReuse() {
super.prepareForReuse()
self.delegate = nil
}
#IBAction func buttonTapped()
{
self.delegate!.didTapOnButton(self)
}
In your tableview controller
1.Implement delegate method
2.Inside cellForRowAtIndexPath assign tag value to cell.button
3.implement this method
func didTapOnButton(_ cell: Cell) {
print("off button clicked at index \(cell.button.tag)")
}

Resources