Uigesturerecognizer and Uitableviewcell Issue

UIGestureRecognizer and UITableViewCell issue

Instead of adding the gesture recognizer to the cell directly, you can add it to the tableview in viewDidLoad.

In the didSwipe-Method you can determine the affected IndexPath and cell as follows:

-(void)didSwipe:(UIGestureRecognizer *)gestureRecognizer {

if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
CGPoint swipeLocation = [gestureRecognizer locationInView:self.tableView];
NSIndexPath *swipedIndexPath = [self.tableView indexPathForRowAtPoint:swipeLocation];
UITableViewCell* swipedCell = [self.tableView cellForRowAtIndexPath:swipedIndexPath];
// ...
}
}

Performance Issue with UITableViewCell GestureReognizer

The cellForRowAtIndexPath-method gets called multiple times for the same NSIndexPath so you add too many gesture recognizers to the cells. Thus the performance will suffer.

My first suggestion would be to add only one gesture recognizer to the table view.
(I wrote this answer for a simililar question: https://stackoverflow.com/a/4604667/550177 )

But as you said, it causes issues with the searchDisplayController. Maybe you can avoid them with a smart implementaion of the UIGestureRecognizerDelegate (return NO in -(BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer; when the tap occurred not within a cell).

My second suggestion: Add the gesture recognizers only once:

if ([cell.gestureRecognizers count] == 0) {
// add recognizer for single tap + double tap
}

UITapGestureRecognizer breaks UITableView didSelectRowAtIndexPath

Ok, finally found it after some searching through gesture recognizer docs.

The solution was to implement UIGestureRecognizerDelegate and add the following:

#pragma mark UIGestureRecognizerDelegate methods

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if ([touch.view isDescendantOfView:autocompleteTableView]) {

// Don't let selections of auto-complete entries fire the
// gesture recognizer
return NO;
}

return YES;
}

That took care of it. Hopefully this will help others as well.

How to add gesture to UITableViewCell?

To add gesture to UITableViewCell, you can follow the steps below:

First, add gesture recognizer to UITableView

tapGesture = UITapGestureRecognizer(target: self, action: #selector(tableViewController.tapEdit(_:)))
tableView.addGestureRecognizer(tapGesture!)
tapGesture!.delegate = self

Then, define the selector. Use recognizer.locationInView to locate the cell you tap in tableView. And you can access the data in your dataSource by tapIndexPath, which is the indexPath of the cell the user tapped.

func tapEdit(recognizer: UITapGestureRecognizer)  {
if recognizer.state == UIGestureRecognizerState.Ended {
let tapLocation = recognizer.locationInView(self.tableView)
if let tapIndexPath = self.tableView.indexPathForRowAtPoint(tapLocation) {
if let tappedCell = self.tableView.cellForRowAtIndexPath(tapIndexPath) as? MyTableViewCell {
//do what you want to cell here

}
}
}
}

It is possible to add gesture directly to TableView cell and access the datasource in viewController, You need to set up a delegate:

In your custom cell:

import UIKit


class MyTableViewCell: UITableViewCell {

var delegate: myTableDelegate?

override func awakeFromNib() {
super.awakeFromNib()

let tapGesture = UITapGestureRecognizer(target: self, action: #selector(MyTableViewCell.tapEdit(_:)))
addGestureRecognizer(tapGesture)
//tapGesture.delegate = ViewController()

}

func tapEdit(sender: UITapGestureRecognizer) {
delegate?.myTableDelegate()
}

}

protocol myTableDelegate {
func myTableDelegate()
}

In your viewController:

import UIKit

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate, myTableDelegate {

@IBOutlet var tableView: UITableView!

override func viewDidLoad() {
super.viewDidLoad()

tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view, typically from a nib.
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 35
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as? MyTableViewCell

cell?.delegate = self

return cell!
}

func myTableDelegate() {
print("tapped")
//modify your datasource here
}

}

However, this method could cause problems, see UIGestureRecognizer and UITableViewCell issue. In this case, when the swipe gesture successes, the selector get called twice for some reason. I can't say the second method is a bad one as I haven't found any direct evidence yet, but after searching through Google, it seems like the first method is the standard way.

iOS - UIGestureRecognizer In UITableViewCell Similar To Snapchat

You can create a custom TableViewCell for this. Code below should do what you're asking. Just need to add the delegate to your ViewController

protocol TableViewCellDelegate {
func something()
}

class TableViewCell: UITableViewCell {

var startSegue = false
var label: UILabel // Or UIView, whatever you want to show
var delegate: TableViewCellDelegate? // Delegate to your ViewController to perform the segue

required init(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}

override init(style: UITableViewCellStyle, reuseIdentifier: String?) {

// Utility method for creating the label / view
func createLabel() -> UILabel {
let label = UILabel(frame: CGRect.nullRect)

// Add label customization here

return label
}

// Create "check" & "cross" labels for context cues
label = createLabel() // Create the label
label.text = "\u{2713}" // Add check symbol as text

super.init(style: style, reuseIdentifier: reuseIdentifier)

addSubview(label)

// Add a pan recognizer
var recognizer = UIPanGestureRecognizer(target: self, action: "handleSwipe:")
recognizer.delegate = self
addGestureRecognizer(recognizer)
}

let cueMargin: CGFloat = 10.0, cueWidth: CGFloat = 50.0

override func layoutSubviews() {

// The label starts out of view
label.frame = CGRect(x: bounds.size.width + cueMargin, y: 0, width: cueWidth, height: bounds.size.height)
}

func handleSwipe(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translationInView(self)

if recognizer.state == .Changed {
center = CGPointMake(center.x + translation.x, center.y)
startSegue = frame.origin.x < -frame.size.width / 2.0 // If swiped over 50%, return true
label.textColor = startSegue ? UIColor.redColor() : UIColor.whiteColor() // If swiped over 50%, become red
recognizer.setTranslation(CGPointZero, inView: self)
}
if recognizer.state == .Ended {
let originalFrame = CGRect(x: 0, y: frame.origin.y, width: bounds.size.width, height: bounds.size.height)
if delegate != nil {
startSegue ? delegate!.something() : UIView.animateWithDuration(0.2) { self.frame = originalFrame }
}
}
}

// Need this to handle the conflict between vertical swipes of tableviewgesture and pangesture
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer {
let velocity = panGestureRecognizer.velocityInView(superview!)
if fabs(velocity.x) >= fabs(velocity.y) { return true }
return false
}
return false
}
}

I got this from this tutorial

EDIT:
In your ViewController you'll have to 'register' this TableViewCell by using:

yourTableName.registerClass(TableViewCell.self, forCellReuseIdentifier: "cellIdentifier")

And your TableViewDataSource for cellForRowAtIndexPath would look like:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath) as TableViewCell

cell.delegate = self

// Add stuff for your other labels like:
// "cell.name = item.name" etc

return cell
}

If you want to pass data back to the ViewController, just use the delegate.

UIGestureRecognizer on custom UITableViewCell. Tapping fires on multiple cells

I would recommend separating your model from your view. What this means is that you should keep track of which cells should render as flipped, but not on the cell itself. Lets say you declare an NSArray (probably NSMutableArray, even) called flippedIndexes in your view controller.

If your data set is as big as you say, then probably you should use a sparse array instead of NSArray. Sparse arrays can easily be implemented with NSMutableDictionary. NSMutableSet could also work.

When dequeueing the cell, you would then check which cells should be flipped, probably at that configureCell:atIndexPath: method of yours. Basically, if the indexPath shows on your sparse array or set you render the cell as flipped. This would imply dropping the cellFlag property you have declared on your cell and toggling its state according to the model I've been mentioning. For flipping a cell, check the flippedIndexes property for the given indexPath and act accordingly. Something like this:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = (MyTableViewCell*)[tableView cellForRowAtIndexPath:indexPath];

if (![self.flippedIndexes containsObject:indexPath])
{
[cell cellFrontTapped];
[self.flippedIndexes addObject:indexPath];
}
else
{
[cell cellBackTapped];
[self.flippedIndexes removeObject:indexPath];
}
}

Here I'm assuming the use of NSMutableSet for the flippedIndexes property. Notice that this will only work properly if you also check the model in configureCell:atIndexPath:, as otherwise you'll have cells magically clearing when scrolling.

Moral of the story is: don't store state information in queued cells.

UITableView on a UIView with UITapGestureRecognizer (cell selection doesn't work)

When you create your UITapGestureRecogizer set the setCancelsTouchesInView property to NO. That should allow your UITableViewCell to receive the touch.

Problems using UIPanGestureRecognizer in UITableViewCell

If you don't want the cell's swipe gesture to happen simultaneously with the table view scroll gesture, then add a pan gesture to your cell and make it a delegate:

UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(doPan:)];
pan.delegate = self;
[self addGestureRecognizer:pan];

And implement the following delegate method to only start if the pan is horizontal:

#pragma mark - UIGestureRecognizerDelegate

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
// note: we might be called from an internal UITableViewCell long press gesture

if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {

UIPanGestureRecognizer *panGestureRecognizer = (UIPanGestureRecognizer*)gestureRecognizer;
UIView *cell = [panGestureRecognizer view];
CGPoint translation = [panGestureRecognizer translationInView:[cell superview]];

// Check for horizontal gesture
if (fabs(translation.x) > fabs(translation.y))
{
return YES;
}

}

return NO;
}

Swift3 ..

override func awakeFromNib() {
super.awakeFromNib()
// do not use, say, layoutSubviews as layoutSubviews is called often
let p = UIPanGestureRecognizer(target: self, action: #selector(yourPan))
p.delegate = self
contentView.addGestureRecognizer(p)
}
}

override func gestureRecognizerShouldBegin(_ g: UIGestureRecognizer) -> Bool {
if (g.isKind(of: UIPanGestureRecognizer.self)) {
let t = (g as! UIPanGestureRecognizer).translation(in: contentView)
let verticalness = abs(t.y)
if (verticalness > 0) {
print("ignore vertical motion in the pan ...")
print("the event engine will >pass on the gesture< to the scroll view")
return false
}
}
return true
}


Related Topics



Leave a reply



Submit