How to avoid adding more than one tap gesture to any cell? - ios

I have a weird problem. When scrolling down, cells disappear if Tap Gesture happened.
Looks like I need to stop adding Tap Gesture to cells. I've done testing of this condition in function but it didn't work.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ToDoItemsCell
...
cell.textField.delegate = self
cell.textField.isHidden = true
cell.toDoItemLabel.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toDoItemLabelTapped))
tapGesture.numberOfTapsRequired = 1
cell.addGestureRecognizer(tapGesture)
return cell
}
And here is my function:
#objc func toDoItemLabelTapped(_ gesture: UITapGestureRecognizer) {
if gesture.state == .ended {
let location = gesture.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: location) {
if let cell = self.tableView.cellForRow(at: indexPath) as? ToDoItemsCell {
cell.toDoItemLabel.isHidden = true
cell.textField.isHidden = false
cell.textField.becomeFirstResponder()
cell.textField.text = cell.toDoItemLabel.text
}
}
}
}
Tapping works, but it keeps adding to other cells and makes them disappear. What can be the issue?

Gesture should be added once to each cell. In your code gesture will be added every time cellForRowAt will be called and it will be called many times especially when you scroll down to list.
Move you gesture add code to ToDoItemsCell class and than you can use delegates to inform your view controller when cell gets tapped.
protocol ToDoItemsCellDelegate {
toDoItemsCellDidTapped(_ cell: ToDoItemsCell)
}
class ToDoItemsCell : UITableViewCell {
weak var delegate: ToDoItemsCellDelegate?
var indexPath: IndexPath!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
// code common to all your cells goes here
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(toDoItemLabelTapped))
tapGesture.numberOfTapsRequired = 1
self.addGestureRecognizer(tapGesture)
}
#objc func toDoItemLabelTapped(_ gesture: UITapGestureRecognizer) {
delegate?.toDoItemsCellDidTapped(self)
}
}
In function cellForRowAt you can just select the delegate and set indexPath.
Note:
If you just wanted to perform action when user taps any cell you can use didSelectRowAt method of UITableViewDelegate.

Related

Does tap gestures added to cell gets preserved on reuse?

I have a cell with an image. I am adding tap gesture to it tableview delegate method. When the cell gets reused, does the tap gesture duplicated? What happens to the tap gesture?
class CalendarCell: UITableViewCell {
#IBOutlet weak var locationImageView: UIImageView!
}
class CalendarViewController: UIViewController {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "calendarCell") as! CalendarCell
let locationLabelTap = UITapGestureRecognizer(target: self, action: #selector(locationDidTap(recognizer:)))
cell.locationLabel.addGestureRecognizer(locationLabelTap)
return cell
}
#objc func locationDidTap(recognizer: UITapGestureRecognizer) {
}
}
Short answer: Yes
Long answer:
You shouldn't be doing it like that. Add the tap gesture when the cell is initialized. This way the tap is added only once when it is created and not everytime it is reused.
class CalendarCell: UITableViewCell {
//Your variable declaration and other stuff
.
.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
//Adding subviews, constraints and other stuff
.
.
.
let locationLabelTap = UITapGestureRecognizer(target: self, action: #selector(locationDidTap(recognizer:)))
locationLabel.addGestureRecognizer(locationLabelTap)
}
.
.
.
}
If you are using a storyboard, you should do the same in awakeFromNib file as pointed out by #DuncanC.
override func awakeFromNib() {
super.awakeFromNib()
.
.
.
let locationLabelTap = UITapGestureRecognizer(target: self, action: #selector(locationDidTap(recognizer:)))
locationLabel.addGestureRecognizer(locationLabelTap)
}

Adding a gesture recognizer to an image view in a table cell

How can I add a Gesture Recognizer to a UIImageView in a table cell? I want it so that if a user taps an image in the cell, the image will change and the data model will update.
I know this needs to be set up in the UITableViewController. My code currently can execute a command if anywhere in the cell is tapped, but I would like it to execute only if the image is tapped, not anywhere in the cell.
I setup up the gesture recognizer in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// Load sample data
loadSampleHabits()
// Initialize tap gesture recognizer
var recognizer = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
// Add gesture recognizer to the view
self.tableView.addGestureRecognizer(recognizer)
And this is the function
//action method for gesture recognizer
func tapEdit(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.ended {
let tapLocation = recognizer.location(in: self.tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) as? HabitTableViewCell {
print("Row Selected")
}
}
}
As a secondary question, are there any conflicts if I want to add a gesture recognizer to the cell and the image view within the cell?
You are adding gesture recognizer on your tableview instead of imageView as you required. Yo need to move your code from viewDidLoad to cellForRowAtIndexPath and add gesture to imageView in each cell while configuing your cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
var recognizer = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
// Add gesture recognizer to your image view
cell.yourimageview.addGestureRecognizer(recognizer)
}
Note: Do make sure to enable userinteraction of your image view
cell.yourimageview.userInteractionEnabled = YES;
For your requirement I will suggest using UILongPressGestureRecognizer as it has less chances of conflict in gesture and didselect. Yo can add UILongPressGestureRecognizer in viewDidLoad and access it as per your requirement.
let lpgr = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.handleLongPress(_:)))
lpgr.minimumPressDuration = 1
tableView.addGestureRecognizer(lpgr)
Define method as
func handleLongPress(_ gesture: UILongPressGestureRecognizer){
if gesture.state != .began { return }
let tapLocation = gesture.location(in: self.tableView)
if let tapIndexPath = self.tableView.indexPathForRow(at: tapLocation) {
if let tappedCell = self.tableView.cellForRow(at: tapIndexPath) as? HabitTableViewCell {
print("Row Selected")
}
}
You can try removing if recognizer.state == UIGestureRecognizerState.ended condition from your method.
UITapGestureRecognizer is a discrete gesture, and as such, your event handler is called only once when the gesture was recognized. You don't have to check the state at all. Certainly you won't receive a call for the state of .Began. For more info consider #Rob ans here.
Add This line in cell for row at index path
var recognizer = UITapGestureRecognizer(target: self, action: #selector(tapEdit(recognizer:)))
// Add gesture recognizer to the view
cell.yourimageviewname.addGestureRecognizer(recognizer)
cell.yourimageviewname.userInteractionEnabled = true;
For my suggestion you have to use UIButton in cell, for performance
improvements,
UIButtons
Specially designed for this and have been extensively optimized by Apple for touches.
If you want image in cell you can use UIButton with Image inside.
I have had design a solution like this. I just write a sample code below:
import UIKit
protocol CellImageTapDelegate {
func tableCell(didClickedImageOf tableCell: UITableViewCell)
}
class SampleCell : UITableViewCell {
var delegate : CellImageTapDelegate?
var tapGestureRecognizer = UITapGestureRecognizer()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
tapGestureRecognizer.addTarget(self, action: #selector(SampleCell.imageTapped(gestureRecgonizer:)))
self.addGestureRecognizer(tapGestureRecognizer)
}
func imageTapped(gestureRecgonizer: UITapGestureRecognizer) {
delegate?.tableCell(didClickedImageOf: self)
}
}
class ViewController: UITableViewController, CellImageTapDelegate {
// CellImageTapDelegate
func tableCell(didClickedImageOf tableCell: UITableViewCell) {
if let rowIndexPath = tableView.indexPath(for: tableCell) {
print("Row Selected of indexPath: \(rowIndexPath)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SampleCellID", for: indexPath) as! SampleCell
cell.delegate = self
return cell
}
}
remember to do following in storyboard
1. enable user interaction of imageview
2. set class of tableviewcell
3. set reuse identifier of tableviewcell
// create an instance of UITapGestureRecognizer and tell it to run
// an action we'll call "handleTap:"
let tap = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
// we use our delegate
tap.delegate = self
// allow for user interaction
cell.imageViewName.userInteractionEnabled = true
// add tap as a gestureRecognizer to tapView
cell.imageViewName.addGestureRecognizer(tap)
import UIKit
class UserInfoCell: UITableViewCell{
#IBOutlet weak var imagePlaceholder: UIImageView!
}
class ViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UIImagePickerControllerDelegate,UINavigationControllerDelegate {
#IBOutlet weak var tableView: UITableView!
let imagePicker = UIImagePickerController()
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserInfoCell" ,for: indexPath ) as! UserInfoCell
let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.openGallery))
cell.imagePlaceholder.addGestureRecognizer(recognizer)
recognizer.numberOfTapsRequired = 1
cell.imagePlaceholder.isUserInteractionEnabled = true
cell.name.text = "Akshay"
if let data = UserDefaults.standard.data(forKey: "savedImage") {
cell.imagePlaceholder.image = UIImage(data: data as Data)
}
return cell
}
#objc func openGallery(){
imagePicker.sourceType = .photoLibrary
present(imagePicker,animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let userimage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
let imageData = userimage.jpegData(compressionQuality: 1)!
UserDefaults.standard.setValue(imageData, forKey: "savedImage")
print("image found")
self.imagePicker.dismiss(animated: true, completion: nil)
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
imagePicker.delegate = self
tableView.tableFooterView = UIView()
}
}
This code select image from gallery using Tapgesture of ImageView inside a TableViewCell

Adding UITapGestureRecognizer to subview on tableViewCell

everyone, I am trying to add gesture recognizer to StackView which located on TableViewCell with that code and it doesn't work:
#IBOutlet var categoryStackView: UIStackView! {
didSet {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(categoryStackViewTapped))
categoryStackView.addGestureRecognizer(tapGesture)
}
}
And it doesn't work, I checked StackView and it is enable for user interactive
#objc func categoryStackViewTapped(sender: UITapGestureRecognizer) {
print("Here we are")
}
I am suggesting another approach if you have multiple stackView in your tableView. Add the current index row as tag to your , and then add click action to your button handler function.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
cell.stackView.tag = indexPath.row
cell.stackView.addTarget(self, action:#selector(categoryStackViewTapped(sender:)), for: .touchUpInside)
return cell
}
Then in your handler function, you can get the correct index path by reading this tag
func categoryStackViewTapped(sender: UIStackView) {
let myIndexPath = IndexPath(row: sender.tag, section: 0)
//... other code here
}

Finding the indexPath of a cell with a gesture recogniser in handler method

I have a pan gesture recogniser on a UITableViewCell which is attached to a method called didPan(sender: UIPanGestureRecognizer).
How can I use this method to determine which cell in a tableView this was activated from?
A good way to do this is to add the gesture recognizer in the UITableViewCell subclass and also have a delegate property in that class as well. So in your subclass:
protocol MyCustomCellDelegate {
func cell(cell: MyCustomCell, didPan sender: UIPanGestureRecognizer)
}
class MyCustomCell: UITableViewCell {
var delegate: MyCustomCellDelegate?
override func awakeFromNib() {
let gesture = UIPanGestureRecognizer(target: self, action: "panGestureFired:")
contentView.addGestureRecognizer(gesture)
}
func panGestureFired(sender: UIPanGestureRecognizer) {
delegate?.cell(self, didPan: sender)
}
}
Then in cellForRowAtIndexPath you just assign you view controller as the cells delegate.
You could add your pan gesture to the UITableViewCell in cellForRowAtIndexPath.
Then extract the optional UITableViewCell and look up the indexPath in the tableView.
Make sure you setup the UITableView as an IBOutlet so you can get to it in didPan:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PanCell", forIndexPath: indexPath)
let panGesture = UIPanGestureRecognizer(target: self, action: "didPan:")
cell.addGestureRecognizer(panGesture)
return cell
}
func didPan(sender: UIPanGestureRecognizer) {
// Sender will be the UITableViewCell
guard let cell = sender.view as? UITableViewCell else {
return
}
let indexPathForPan = tableView.indexPathForCell(cell)
}

tap gesture on uiimage in collectionview

In each cell of my UICollectionView, I have multiple object to interact with.
So instead of use didSelect delegate method, I really wanted to add a tap gesture on each object of the cell.
To make it simple, I removed all the other objects in the example:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! PlacesCollectionViewCell
let tap = UITapGestureRecognizer(target: self, action: "gotToSelectedPlace:")
tap.numberOfTapsRequired = 1
cell.imageView.userInteractionEnabled = true
cell.imageView.addGestureRecognizer(tap)
cell.imageView.file = places[indexPath.row].picture
cell.imageView.loadInBackground()
return cell
}
In viewDidLoad, I use a nib :
collectionView.registerNib(UINib(nibName: "PlacesCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
UICollectionView Settings:
Delays Content Touches: True
Cancellable Content Touches: True
With this example, I can't handle the tap gesture. Nothing happen.
Did I miss something??
Thanks
try this one
var doubletapgesture : UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "processDoubleTap:")
doubletapgesture.numberOfTapsRequired = 1
collectionView.addGestureRecognizer(doubletapgesture)
now handle gesture
func processDoubleTap (sender: UITapGestureRecognizer)
{
if sender.state == UIGestureRecognizerState.Ended
{
var point:CGPoint = sender.locationInView(collectionView)
var indelPath:NSIndexPath =collectionView.indexPathForItemAtPoint(point)
if indexPath
{
println("image taped")
}
else
{
//Do Some Other Stuff Here That Isnt Related;
}
}
}

Resources