I'm having a difficult time getting interactive reordering to work in a UICollectionView embedded in a subclassed UIViewController. There is no custom layout object.
The view controller is set as the delegate and datasource for the collection view, and the collection view is given a long press gesture recognizer as outlined here and here. I've also implemented the datasource methods canMoveItemAt: and moveItemAt:To:
I have another collection view in the view controller which shouldn't reorder, and the VC is the delegate/datasource of both. Thus:
func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
if collectionView == cardCollectionView {
return true
}
else {
return false
}
}
I can confirm that this datasource method is being called appropriately.
The handler of the long press gesture triggers the appropriate steps, and the cells begin their rearranging behavior during the press. EXCEPT for the response to UIGestureRecognizerState.ended:, which triggers cardCollectionView.endInteractiveMovement(). This should lead to the moveItemAt:To datasource method, but it is never called.
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
//print(cardCollectionView.delegate)
switch(gesture.state) {
case UIGestureRecognizerState.began:
guard let selectedIndexPath = self.cardCollectionView.indexPathForItem(at: gesture.location(in: self.cardCollectionView)) else {
break
}
cardCollectionView.beginInteractiveMovementForItem(at: selectedIndexPath)
case UIGestureRecognizerState.changed:
cardCollectionView.updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!))
case UIGestureRecognizerState.ended:
print("ending")
cardCollectionView.endInteractiveMovement()
default:
print("canceling")
cardCollectionView.cancelInteractiveMovement()
}
}
func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
print("Starting Index: \(sourceIndexPath.item)")
}
Rather, when the long press ends, the cells just all return to their initial location, and I never get a chance to effect the reordering of the data. What do I need to do to complete this reordering process?
Also, when the long press is initiated, the cells bounce around in the reordering animation, but I don't see the "lifted" cell under the press. Is that a separate issue, or are they related?
(Code is in Swift 4.0)
Having a UICollectionViewDropDelegate assigned to a UICollectionView interferes with interactive reordering (iOS 9 style) via the long press gesture handler method.
After tearing down my UIViewController and rebuilding it a line at a time, I isolated the place where interactive reordering failed. The culprit was the one in viewDidLoad where (for other reasons) I assigned the ViewController to be the dropDelegate for the CollectionView:
self.cardCollectionView.dropDelegate = self
The workaround was to simply implement reordering via the new iOS 11 drag and drop API's (WWDC 2017 session 223, around 32:00)
Related
I am running into an issue where I am navigating from one UICollectionView to another upon selection of the cell.
The issue is the flashing of previous data. It only happens when I click "back" on the navigation bar. I have included the code where navigation occurs.
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.selectedIndexPath = indexPath
let vc = self.storyboard?.instantiateViewController(withIdentifier: kSecondVCId) as? SecondCollectionViewController
self.navigationController?.pushViewController(vc!, animated: true)
}
The values are stagnant arrays of values in both views.
Video of issue: https://vimeo.com/263987657
I am using the following library from GitHub, there is an open issue for this but I'm hoping someone will have thoughts on a possible fix.
The collection view reuses the same cell, so its very simple you have to reset the content before reusing them, else the old cache data will show.
reset the data in UICollecitonViewCell (or) UITableViewCell class using this override function(prepare for reuse)
override func prepareForReuse() {
//just reset the text from here
self.Button.setTitle(title: "", for: .normal)
//reset any thing in here which u don't wanna see again
super.prepareForReuse()
}
Hope this helps, some one. Happy coding !
I have a collection view which loads a list of products and there is no fancy functionality in it. It just loads data from API. The problem is that when i try to tap on one of the items in the collection view
collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
is not getting called. But when i long press on a cell it gets called.
I am pretty sure that there are no gestures applied on the cell.
Can anyone give me some guidelines to tackle the problem.
The code base that i have is as follows
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellTapped = (self.storeSearchListingCollectionViewOutlet.cellForItem(at: indexPath)) as! StoreSearchProdListingCollectionViewCell
UStoreShareClass.storeSharedInstance.longTappedProductID = String(cellTapped.tag)
if((UStoreShareClass.storeSharedInstance.longTappedProductID) != nil)
{
self.performSegue(withIdentifier: "searchToProductDetailSegue", sender: self)
}
else
{
SetDefaultWrappers().showAlert(info:SERVER_DOWN_ERROR_ALERT, viewController: self)
}
}
View Hierarchy is as follows
Thanks in Advance...!!!
This is intentional. On the Storyboard go to the Collection View and uncheck "Delay Touch Down" in the Attribute inspector.
May have two reasons:
1) May be first reason is:
Unfortunately your code is not getting main thread to work on UI. So you have to put your UI related part in MAIN thread like this.
DispatchQueue.main.async {
// PUT YOUR CODE HERE
if((UStoreShareClass.storeSharedInstance.longTappedProductID) != nil)
{
self.performSegue(withIdentifier: "searchToProductDetailSegue", sender: self)
}
else
{
SetDefaultWrappers().showAlert(info:SERVER_DOWN_ERROR_ALERT, viewController: self)
}
}
2) In second reason:
Have you made collectionView bouncing turned off?
In Storyboard, make checked "Bounce on scroll" checkbox of collectionView, if unchecked.
For future readers of this old question - probably me in the future ;)
I had a UITapGestureRecognizer which was swallowing my short taps (in the UICollectionView's grandparent) and hence only the old taps were making it through! I recon you have the same problem. You just need to add tapRecognizer.cancelsTouchesInView = false!
let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapDidTouch(sender:)))
tapRecognizer.cancelsTouchesInView = false
scrollView.addGestureRecognizer(tapRecognizer)
First of all , put breakpoint in if condition and check if it gets inside the didselect block. Then,
Try adding your "performsegue" code in main thread like below
if((UStoreShareClass.storeSharedInstance.longTappedProductID) != nil)
{
DispatchQueue.main.async
{
self.performSegue(withIdentifier: "searchToProductDetailSegue", sender: self)
}
}
else
{
SetDefaultWrappers().showAlert(info:SERVER_DOWN_ERROR_ALERT, viewController: self)
}
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()
}
}
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.
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")
}
}