Task
Add a single tap gesture to UICollectionView, do not get in the way of cell selection.
I want some other taps on the no-cell part of the collectionView.
Code
Using XCode8, Swift 3.
override func viewDidLoad() {
...
collectionView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap)))
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print(indexPath)
}
func tap(sender: UITapGestureRecognizer){
print("tapped")
}
Result
Yeah, it gets in the way now. When you tap on cell, it logs "tapped".
Analysis
I check the hitTest return value of the collectionView and the cell. Both returned the tapped cell, which means they form a responder chain of Cell -> CollectionView
no gestures on the cell
3 gestures on collectionView, no one seems to work with the cell select
UIScrollViewDelayedTouchesBeganGestureRecognizer
UIScrollViewPanGestureRecognizer
UITapGestureRecognizer
callStack, seems cell selection has a different stack trace with gesture's target-action pattern.
double tap gesture works along with cell selection.
Question
Couldn't find more trace. Any ideas on how cell selection is implemented or to achieve this task?
Whenever you want to add a gesture recognizer, but not steal the touches from the target view, you should set UIGestureRecognizer.cancelsTouchesInView for your gestureRecognizer instance to false.
Instead of trying to force didSelectItem you can just get the indexPath and/or cell this way:
func tap(sender: UITapGestureRecognizer){
if let indexPath = self.collectionView?.indexPathForItem(at: sender.location(in: self.collectionView)) {
let cell = self.collectionView?.cellForItem(at: indexPath)
print("you can do something with the cell or index path here")
} else {
print("collection view was tapped")
}
}
I get another way: when adding gestures, set delegate, implement below method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
you can decide whether the gesture recognizer to receive the touch by your own logic, with position or the page's hidden property, based on your demand.
Related
I am adding a custom swipe gesture on a UITableViewCell that updates CoreData when a specific cell is swiped. However, I am having trouble passing the indexPath of the swiped cell to the function that will modify the CoreData. I am not using a Table View Swipe Action - I am animating the cell when it is swiped to cross the item out, which means I will need to call an animation on the custom UITableViewCell. So far, I have tried passing the indexPath of the swiped cell to the #selector of the UISwipeGestureRecognizer like so:
The function that handles the gesture:
#objc func handleSwipeGesture(sender: UISwipeGestureRecognizer ,at indexPath: IndexPath) {
itemArray[indexPath.row].complete = !itemArray[indexPath.row].complete
print("\(itemArray[indexPath.row].complete)")
saveItems()
// call animation??
}
In cellForRowAt in the UITableViewDelegate I declare the swipe action and pass the indexPath of the swiped cell as a parameter to the function above.
let swipeToComplete = SwipeCompletion.init(target: self, action: #selector(handleSwipeGesture))
swipeToComplete.indexPath = indexPath
cell.addGestureRecognizer(swipeToComplete)
This is the class that enables me to pass the indexPath through:
class SwipeCompletion: UISwipeGestureRecognizer {
var indexPath: IndexPath!
}
However, this I get the error when I swipe on a cell: EXC_BAD_ACCESS (code=1, address=0xf)
Any ideas on how I could achieve this and how I could call the cell animation on a cell?
On these "#selectors" you can't customize the values passed to the call (As far as I know).
It looks like you have indexPath as a property of your custom recognizer.
Use that indexPath, not the one being passed in. Thats likely not the correct instance.
Something like this might be what your trying to do.
#objc func handleSwipeGesture(sender: SwipeCompletion) {
itemArray[sender.indexPath.row].complete = !itemArray[sender.indexPath.row].complete
print("\(itemArray[sender.indexPath.row].complete)")
saveItems()
//call animation??
}
I have a collectionView showing cells. When I drag/touch/slide my finger on an item, if the touch ends on the item, the cell is selected (segues to the details screen).
Is there any way to limit cell selection (didSelectItemAt indexPath) to a simple tap? i.e it shouldn't select the cell if finger is dragged on an item and the touch ends on it.
Is this the default behavior?
I feel like it might be the cause of a cryptic issue with my custom navigation.
Thanks
Do add Following in your cellForItem
let tap = UITapGestureRecognizer(target: self, action: #selector(cellTapped(tapGestureRecognizer:)))
tap.numberOfTapsRequired = 1
cell.addGestureRecognizer(tap)
And add following function
#IBAction func cellTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
//Do your required work.
}
You can use UITapGestureRecognizer, cause it will only respond on Tap gesture:
#objc func tapAction(_ sender: UITapGestureRecognizer) {
// TODO: - Action you need
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: <CellReuseId>, for: indexPath)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapAction(_:)))
cell.contentView.addGestureRecognizer(tap)
return cell
}
But in this way didSelectItemAt will not work.
I would like my tableView to only react to double taps and not at all to single taps. I am currently using the following code:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(singleTap))
tapGesture.numberOfTapsRequired = 1
view.addGestureRecognizer(tapGesture)
let doubleTapGesture = UITapGestureRecognizer()
doubleTapGesture.numberOfTapsRequired = 2
view.addGestureRecognizer(doubleTapGesture)
tapGesture.require(toFail: doubleTapGesture)
// implement what to do
if userInfo[indexPath.row].identifier == "username" {
editUsername()
}
}
func singleTap() {
// DO NOTHING
}
So basically I have been trying to "redirect" the single tap to a function that does nothing. However, I find that (in the simulator), the tableView sometimes reacts to the single tap, sometimes not. Any help to solve this issue is highly appreciated!
To achieve your goal:
Add tap gesture recognizer on your table view, do not forget to set numberOfTapsRequired = 2
Do not implement didSelectRowAtIndexPath method
To prevent table view cells from changing their background color after single tap, set in interface builder, Attributes Inspector tab, table view "selection" attribute to "No selection" or table view cell "selection" attribute to "None".
If you want to get indexpath of cell being doubletapped, in your gesture recognizer handler method get tap location in tap.view and use indexPathForRowAtPoint method of tableView:
let tapLocationPoint = tap.location(in: tap.view)
let tappedCellIndexPath = tableView.indexPathForRow(at: tapLocationPoint)
I am creating a screen in iOS Xcode 8.3.2 Swift 3.1 that has a horizontally scrolling UICollectionView on it with a single row of images. The problem is, I had thought the "automatic re-ordering" capability was part of the UICollectionView, when it actually is only implemented in the UICollectionViewController. As I just want a portion of the screen to scroll, I don't believe I can use the collection view controller -- the collection view is always full screen in that case, right? With the controller, I discovered installsStandardGestureForInteractiveMovement, which automatically implements a gesture recognizer.
I would like this same capability in my collection view, which is on a UIViewController. However, I have no idea which gestures I need to capture to implement cell re-ordering. Is any of the drag re-ordering "magic" built into the collection view itself? Is there a way I can use the controller, instead, and not have the collection view full screen?
I have implemented the following method to do the actual move of the data, but I'm not sure which gesture recognizer(s) to setup.
func collectionView(_ collectionView: UICollectionView,
moveItemAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath) {
let cellToMove = promoPages.remove(at: (sourceIndexPath as NSIndexPath).row)
promoPages.insert(cellToMove, at: (destinationIndexPath as NSIndexPath).row)
}
After reading this guide, and converting it to Swift3:
For UIViewController class:
override func viewDidLoad() {
super.viewDidLoad()
let g = UILongPressGestureRecognizer(target: self,
action: #selector(handleLongGesture(_:)))
clcImages?.addGestureRecognizer(g)
}
func handleLongGesture(_ gesture: UILongPressGestureRecognizer)
{
switch(gesture.state) {
case UIGestureRecognizerState.began:
guard let selectedIndexPath = clcImages?.indexPathForItem(at: gesture.location(in: clcImages)) else {
break
}
clcImages?.beginInteractiveMovementForItem(at: selectedIndexPath)
case UIGestureRecognizerState.changed:
clcImages?.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case UIGestureRecognizerState.ended:
clcImages?.endInteractiveMovement()
default:
clcImages?.cancelInteractiveMovement()
}
}
I have been working on a new application and it displays Gifs in a Collection View. I am also using a custom collection view cell class for the cells in my collection view.
The method didSelectItemAtIndexPath doesn't work though ...
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
println("it worked")
// ^this did not print
}
How would I change it so I can get the indexPath of the clicked item using a gesture recognizer?
as #santhu said (https://stackoverflow.com/a/21970378/846780)
didSelectItemAtIndexPath is called when none of the subView of
collectionViewCell respond to that touch. As the textView respond to
those touches, so it won't forward those touches to its superView, so
collectionView won't get it.
So, you have a UILongPressGestureRecognizer and it avoid didSelectItemAtIndexPath call.
With UILongPressGestureRecognizer approach you need to handle handleLongPress delegate method. Basically you need to get gestureReconizer.locationInView and know the indexPath located at this point (gestureReconizer.locationInView).
func handleLongPress(gestureReconizer: UILongPressGestureRecognizer) {
if gestureReconizer.state != UIGestureRecognizerState.Ended {
return
}
let p = gestureReconizer.locationInView(self.collectionView)
let indexPath = self.collectionView.indexPathForItemAtPoint(p)
if let index = indexPath {
var cell = self.collectionView.cellForItemAtIndexPath(index)
// do stuff with your cell, for example print the indexPath
println(index.row)
} else {
println("Could not find index path")
}
}