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
How to Detect If a Video File Was Recorded in Portrait Orientation, or Landscape in iOS
Xcode 6.1 Missing Required Architecture X86_64 in File
How to Animate the Change of Image in an Uiimageview
Change the Uitableviewcell Height According to Amount of Text
How to Click a Button Behind a Transparent Uiview
How to Create a Scroll View with a Page Control Using Swift
What's Nslocalizedstring Equivalent in Swift
Loading an Image into Uiimage Asynchronously
How to Create a Round Cornered Uilabel on the Iphone
Invalid Update: Invalid Number of Rows in Section 0
React Native Responsive Font Size
Tutorial for Slcomposeviewcontroller Sharing
Attempt to Insert Non-Property List Object When Trying to Save a Custom Object in Swift 3
Xcode 9 - "Fixed Width Constraints May Cause Clipping" and Other Localization Warnings
Inconsistent Unicode Emoji Glyphs/Symbols