Swift: Delay in UITableViewCell Selection - ios

I have a UITableView inside of another view. Whenever I click on a cell in the table, I would like that cell to be highlighted until it is clicked again to deselect it. Using the didSelectRowAtIndexPath method, I have accomplished this. However, the cell selection takes a long time. I have to hold down on a cell for 3 seconds before it highlights the cell, rather than it being instantaneous. How do I get it to select the cell the instant it is touched?
Here is my relevant code.
class AddDataViewController : UIViewController {
#IBOutlet weak var locationTableView: UITableView!
var fstViewController : FirstViewController?
let locationTableViewController = LocationTableViewController()
override func viewWillAppear(animated: Bool) {
// Set the data source and delgate for the tables
self.locationTableView.delegate = self.locationTableViewController
self.locationTableView.dataSource = self.locationTableViewController
// Set the cell separator style of the tables to none
self.locationTableView.separatorStyle = UITableViewCellSeparatorStyle.None
// Refresh the table
self.locationTableView.reloadData()
}
override func viewDidLoad(){
super.viewDidLoad()
// Create a tap gesture recognizer for dismissing the keyboard
let tapRecognizer = UITapGestureRecognizer()
// Set the action of the tap gesture recognizer
tapRecognizer.addTarget(self, action: "dismissKeyboard")
// Add the tap gesture recognizer to the view
//self.view.addGestureRecognizer(tapRecognizer)
}
}
class LocationTableViewController : UITableViewController, UITableViewDelegate, UITableViewDataSource {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return tableView.frame.height / 5
}
override func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.greenColor()
}
override func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.whiteColor()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.blueColor()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: "addDataCell")
cell.selectionStyle = UITableViewCellSelectionStyle.None
cell.textLabel!.text = "Test"
return cell
}
}

The problem was the UITapGestureRecognizer was interfering with the tap of the cell. I apologize that the tap gesture code was not in my initial post as I did not realize that could be the culprit. I have added it into the code snippet in the original post.

This should work but it would be much cleaner to make a custom cell subclass.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCellStyle.Value2, reuseIdentifier: "addDataCell")
cell.textLabel!.text = "Test"
let backgroundView = UIView()
backgroundView.backgroundColor = YOUR_COLOR
cell.selectedBackgroundView = backgroundView
return cell
}
Remove because we want cell selection
cell.selectionStyle = UITableViewCellSelectionStyle.None
Remove since this is already taken care of in the cell subclass
override func tableView(tableView: UITableView, didHighlightRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.greenColor()
}
override func tableView(tableView: UITableView, didUnhighlightRowAtIndexPath indexPath: NSIndexPath) {
tableView.cellForRowAtIndexPath(indexPath)?.backgroundColor = UIColor.whiteColor()
}

I don't have enough reputation points to reply to #Jason247, so hence typing it as an answer. He's right. I had the same issue with a delay of the cells registering a click. Commented out the UITapGestureRecognizer and the delay went away.
In my context, I was using a UISearchBar. I replaced my UITapGestureRecognizer with the search bar's optional delegate method:
class ViewController: UIViewController, UISearchBarDelegate{
func searchBarSearchButtonClicked(searchBar: UISearchBar)
{
self.mySearchBar.endEditing(true)
}
}
You can find more solutions for dismissing the keyboard when using a UISearchBar here

Related

allowsMultipleSelectionDuringEditing not working with Custom UITableviewCell

Referance Link that I have used
Here is my code
class ViewController: UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true
tableView.setEditing(true, animated: false)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
It was Work fine for the default UITableViewCell. But if I have do the same thing with Custom UITableViewCell then selection is not worling
Code with Custom Cell
class ViewController: UITableViewController{
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelectionDuringEditing = true
tableView.setEditing(true, animated: false)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customcell", for: indexPath) as! ProductTblCell
cell.lblProductTitle?.text = "\(indexPath.row)"
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
ProductTblCell Calss
class ProductTblCell: UITableViewCell {
#IBOutlet weak var lblProductTitle: UILabel!
}
Can you please someone tell me what's going Wrong? Thanks in advance
Output
It was, as tends to be the case, my mistake
After searching a lot I realized the issue.
The problem was mostly we all set the cell's selectionStyle property to .none from Storyboard or programmatically to remove the background color of the cell during the selection.
Keep in mind that if you want to use the default selection or
multiple selections feature during the editing must you need to follow this
Set the Selection Style from Storyboard
You can also Set in table views cellForRowAt method
cell.selectionStyle = .none
To remove or change the background color of the cell for the selection you need to use this override method in UITableViewCell
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.backgroundColor = selected ? .gray : .white
}

UITableView tableView(_:didEndEditingRowAt:) not called if rapidly tapping during swipe

I have a UITableView with an editAction that is accessible by swiping left.
It seems like there is a built in UITableView functionality where if you swipe the cell to show the edit action, and then later tap anywhere in the tableView, the action is swiped closed automatically, and didEndEditingRowAt is called to notify the delegate that editing is over.
However, the problem I am seeing is that sometimes, if you swipe to the left and then really quickly after the swipe (when only a tiny piece of the edit action is visible and the animation is in progress), you tap anywhere else on the screen, the edit action is closed but the didEndEditingRowAt is not called!
So, with the following code, we end up with the tableView being in Edit Mode, but no view swiped open, and the last line printed being Will Edit, confirming that didEndEditingRowAt was never called.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view = tableView
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "foo")
tableView.delegate = self
tableView.dataSource = self
tableView.allowsSelectionDuringEditing = false
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let deleteAction = UITableViewRowAction(style: .normal, title: " Remove") {(_, indexPath) in print("OK") }
return [deleteAction]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {}
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
return indexPath
}
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
print("Will edit")
}
func tableView(_ tableView: UITableView, didEndEditingRowAt indexPath: IndexPath?) {
print("Did end edit")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 80
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "foo") ?? UITableViewCell()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
}
Now, this only happens sometimes, and its a bit hard to get the timing right, but its definitely reproducible.
Here is a link to the whole demo: https://github.com/gregkerzhner/SwipeBugDemo
Am I doing or expecting something wrong here? In my real project I have code that fades the other cells to focus on the cell being currently edited, and I end up in a bad state where the other cells get faded, but the focused cell doesn't have any edit actions open.
This is definitely a bug in UITableView, it stays in "editing" state even if the swipe bounced back (e.g. if you swipe it just a little bit).
Good news is that you can employ some of UITableViewCell's methods to find a workaround.
UITableViewCell has corresponding methods that notify of action-related state changes:
func willTransition(to state: UITableViewCellStateMask)
func didTransition(to state: UITableViewCellStateMask)
func setEditing(_ editing: Bool, animated: Bool)
var showingDeleteConfirmation: Bool { get }
The transition methods are called when the transition (and animation) begins and ends. When you swipe, willTransition and didTransition will be called (with state .showingDeleteConfirmationMask), and showingDeleteConfirmation will be true. setEditing is also called with true.
While this is still buggy (cell shouldn't successfully become editing unless you actually unveiled the buttons), didTransition is a callback where you get a chance to check whether the actions view is indeed visible. I don't think there's any robust way to do this, but maybe simply checking that cell's contentView takes most of its bounds would be enough.
So in the end, the working solution ended up a bit different from what #ncke and #hybridcattt suggested.
The problem with #ncke's solution is that the func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) does not get called during this swipe/tap interaction, so the workaround never gets called.
The problem with #hybridcattt's solution is that those UITableViewCell callbacks get called too early, so if you do the swipe rapid tap action, the UITableViewCellDeleteConfirmationView is still part of the subviews of the cell when all of those callbacks get called.
The best way seems to be to override the willRemoveSubview(_ subview: UIView) function of UITableViewCell. This gets called reliably every time the UITableViewCellDeleteConfirmationView gets removed, both during normal swipe close, and also in this buggy swipe rapid tap scenario.
protocol BugFixDelegate {
func editingEnded(cell: UITableViewCell)
}
class CustomCell: UITableViewCell {
weak var bugFixDelegate: BugFixDelegate?
override func willRemoveSubview(_ subview: UIView) {
guard String(describing: type(of: subview)) == "UITableViewCellDeleteConfirmationView" else {return }
endEditing(true)
bugFixDelegate.editingEnded(cell: self)
}
}
As #hybridcattt and #ncke suggested, in your controller you can hook into this delegate and send the missing events to the UITableView and UITableViewDelegate like
class DummyController: UIViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) as? CustomCell else {return UITableViewCell()}
cell.bugFixDelegate = self
}
}
extension DummyController: BugFixDelegate {
//do all the missing stuff that was supposed to happen automatically
func editingEnded(cell: UITableViewCell) {
guard let indexPath = self.tableView.indexPath(for: cell) else {return}
self.tableView.setEditing(false, animated: false)
self.tableView.delegate?.tableView?(tableView, didEndEditingRowAt: indexPath)
}
}
I'm not saying that this is the best thing ever, but if you want a workaround this could be a start:
extension UITableView {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
for cell in self.visibleCells {
if cell.isEditing && (cell.subviews.count < 3 || cell.subviews[2].frame.origin.x < 30.0) {
print("\(cell) is no longer editing")
cell.endEditing(true)
if let indexPath = self.indexPath(for: cell) {
self.delegate?.tableView?(self, didEndEditingRowAt: indexPath)
}
}
}
}
}
The idea is that a UITableView is a subclass of UIScrollView. Whilst the former's delegate methods seem broken, the latter's are still being called. Some experimentation produced this test.
Just an idea :) You may prefer to subclass UITableView rather than extend, to localise the hack.
Here is my solution. Enjoy.
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
if #available(iOS 10.0, *) {
return .none;
} else {
return .delete;
}
}

How to use checkmark for UITableView

I am trying to create a simple todo list app for learning purposes i. I want to be able to click on a row and add a check mark and when clicked again i want it to go away. i have looked at several other examples but nothing is working. How can i achieve this?
class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tbView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tbView.reloadData()
}
override func viewWillAppear(animated: Bool) {
self.tbView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return tasks.manager.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Default Tasks")
//Assign the contents of our var "items" to the textLabel of each cell
cell.textLabel!.text = tasks.manager[indexPath.row].name
cell.detailTextLabel!.text = tasks.manager[indexPath.row].time
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath){
if (editingStyle == UITableViewCellEditingStyle.Delete){
tasks.manager.removeAtIndex(indexPath.row)
tbView.reloadData()
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell!.accessoryType = .Checkmark
tableView.reloadData()
}
}
To put it simply, you can use this code.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath)
if (cell!.accessoryType == .Checkmark) {
cell!.accessoryType = .None
} else {
cell!.accessoryType = .Checkmark
}
tableView.reloadData()
}
This way, when you re-select cell, the checkmark will adjust accordingly.
However, you shouldn't modify cell checkmark this way as the cell will get re used when you scroll.
You need to update your data model instead it above approach.
so in your data model, add new property to hold checkmark state and then use it in cellForRowAtIndexPath function.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Default Tasks")
//Assign the contents of our var "items" to the textLabel of each cell
cell.textLabel!.text = tasks.manager[indexPath.row].name
cell.detailTextLabel!.text = tasks.manager[indexPath.row].time
if tasks.manager[indexPath.row].isSelected { //assume that isSelected is bool
cell!.accessoryType = .Checkmark
} else {
cell!.accessoryType = .None
}
return cell
}
and then in your didSelectRowAtIndexPath update the model.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let dataModel = tasks.manager[indexPath.row]
dataModel.isSelected = !dataModel.isSelected
tableView.reloadData()
}
I'm assuming that you are using a table view in a storyboard. If that's the case, in the attributes inspector, you can choose a chick mark as the style.
You'll need both didSelectRowAtIndexPath and didDeselectRowAtIndexPath methods of the UITableViewDelegate protocol.
You can simple use them like this!
Deselect
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell!.accessoryType = .None
//tableView.reloadData()
}
Select
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let cell = tableView.cellForRowAtIndexPath(indexPath)
cell!.accessoryType = .Checkmark
//tableView.reloadData()
}

Table view didselectrowatindex returning nil (Swift)

I have a UITableViewController that has a function named didselectrowatindex that should return the text on a cell, but it returns nil. How can I return the text of the selected cell in Swift UITableViewController?
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let indexPath = tableView.indexPathForSelectedRow();
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!;
println(currentCell.textLabel!.text)
}
}
EDIT: I forgot to mention that I am using static cells, which means the data is already set so I didn't think there was a need for cellforrowatindex.
It looks like you need to implement cellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell")
let text = self.tableContents[indexPath.row] as! String
//tableContents is just the array from which you're getting what's going in your tableView, and should be declared outside of your methods
cell.textLabel?.text = text
return cell
}
also add
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "cell")
into you viewDidLoad
hope this helps!
Edit for static cells:
Okay, so looking at it that way, maybe try to replace the line
let currentCell = tableView.cellForRowAtIndexPath(indexPath!) as UITableViewCell!;
with
let currentCell = super.tableView(self.tableView, cellForRowAtIndexPath: indexPath)
and get rid of the line above it as suggested by Lancelot in a comment on your question.
Edit Two:
I used the following code and it works:
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let currentCell = super.tableView(self.tableView, cellForRowAtIndexPath: indexPath)
let text: String = currentCell.textLabel!.text as String!
print(text)
}
}
If you have your data source array of the titles as:
var arrTitlesDataSource = ["t1","t2","t3"]
So you are using them in order to set the title at the cellForRowlike this:
cell.textLabel?.text = arrTitlesDataSource[indexPath.row]
Than you may get the title in this way:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var title = arrTitlesDataSource[indexPath.row]
println(title)
}
Get selected row cell from tableView(sender) and then
get your data from selected cell.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var currentCell= tableView.cellForRowAtIndexPath(indexPath)! as tableViewCell
println(currentCell.textLabel!.text)
}

UITableView - Interaction with the table view disappearing everything

I have a UITableView, and added it as a subview of a UIView.
Whenever I select a cell that had everything disappears in UITableView.
Already had this problem at other times, however it stopped happening without changes on code.
Currently I use Swift, however already happened in Objective-C.
Code:
TableView delegates:
numberOfSectionsInTableView
numberOfRowsInSection
cellForRowAtIndexPath
heightForRowAtIndexPath
didSelectRowAtIndexPath
Also registered the identifier of the cell
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
listObjectsToList = ["String 1","String 2","String 3"]
self.view.frame.size = CGSizeMake(300, 110)
self.view.layer.cornerRadius = 5
self.view.layer.masksToBounds = true
self.tableView.scrollEnabled = false
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return listObjectsToList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
cell.textLabel.text = listObjectsToList.objectAtIndex(indexPath.row) as? String
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 40
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.tableView.deselectRowAtIndexPath(indexPath, animated: true)
let objectSelected: AnyObject = listObjectsToList.objectAtIndex(indexPath.row)
object.type = objectSelected as String
self.view.removeFromSuperview()
}
However the method didSelectRowAtIndexPath is not called...
Code adding tableview:
var listTypeTableViewController = ListTypeTableViewController()
listTypeTableViewController.view.frame = CGRectMake(10, 80, 300, 130)
self.view.addSubview(listTypeTableViewController.view)
I have tried:
var listTypeTableViewController = ListTypeTableViewController()
listTypeTableViewController.view.frame = CGRectMake(10, 80, 300, 130)
self.view.addSubview(listTypeTableViewController.tableView)
But without success
The problem was and I was just trying to extract the UITableView to show it. When I need to add a UITableViewController as a child of my UIViewController:
var listTypeTableViewController = ListTypeTableViewController(nibName: "TableViewTypeScheduling", bundle: nil)
listTypeTableViewController.view.frame = CGRectMake(10, 80, 300, 130)
addChildViewController(listTypeTableViewController)
view.addSubview(listTypeTableViewController.view)
The reason is this line in tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) method:
self.view.removeFromSuperview()
You remove a view and all of its superviews, including the table view.

Resources