didSelectItemAtIndexPath Doesn't Work In Collection View Swift - ios

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")
}
}

Related

Implementing re-ordering gestures for UICollectionView

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()
}
}

How to add tap gesture to UICollectionView , while maintaining cell selection?

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.

Cells insight UICollectionView do not load until there are visible?

I have a UITableView with UICollectionView insight every table view cell. I use the UICollectionView view as a gallery (collection view with paging). My logic is like this:
Insight the method
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// This is a dictionary with an index (for the table view row),
// and an array with the url's of the images
self.allImagesSlideshow[indexPath.row] = allImages
// Calling reloadData so all the collection view cells insight
// this table view cell start downloading there images
myCell.collectionView.reloadData()
}
I call collectionView.reloadData() and in the
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// This method is called from the cellForRowAtIndexPath of the Table
// view but only once for the visible cell, not for the all cells,
// so I cannot start downloading the images
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! PhotoCollectionCell
if self.allImagesSlideshow[collectionView.tag] != nil {
var arr:[String]? = self.allImagesSlideshow[collectionView.tag]!
if let arr = arr {
if indexPath.item < arr.count {
var imageName:String? = arr[indexPath.item]
if let imageName = imageName {
var escapedAddress:String? = imageName.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
if let escapedAddress = escapedAddress {
var url:NSURL? = NSURL(string: escapedAddress)
if let url = url {
cell.imageOutlet.contentMode = UIViewContentMode.ScaleAspectFill
cell.imageOutlet.hnk_setImageFromURL(url, placeholder: UIImage(named: "placeholderImage.png"), format: nil, failure: nil, success: nil)
}
}
}
}
}
}
return cell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if self.allImagesSlideshow[collectionView.tag] != nil {
var arr:[String]? = self.allImagesSlideshow[collectionView.tag]!
if let arr = arr {
println("collection row: \(collectionView.tag), items:\(arr.count)")
return arr.count
}
}
return 0
}
I set the right image for the cell. The problem is that the above method is called only for the first collection view cell. So when the user swipe to the next collection view cell the above method is called again but and there is a delay while the image is downloaded. I would like all the collection view cells to be loaded insight every visible table view cell, not only the first one.
Using the image I have posted, "Collection View Cell (number 0)" is loaded every time but "Collection View Cell (number 1)" is loaded only when the user swipe to it. How I can force calling the above method for every cell of the collection view, not only for the visible one? I would like to start the downloading process before swiping of the user.
Thank you!
you're right. the function func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell will be called only when cell start to appear. that's a solution of apple called "Lazy loading". imagine your table / collection view have thousand of row, and all of those init at the same time, that's very terrible with both memory and processor. so apple decide to init only view need to be displayed.
and for loading image, you can use some asynchronous loader like
https://github.com/rs/SDWebImage
it's powerful and useful too :D

Getting UITableViewCell from UITapGestureRecognizer

I have a UITableView with multiple sections and in my cellForRowAtIndexPath method I add a UITapGestureRecognizer to the cell's imageView (each cell has a small image). I have been successful in accessing that image (in order to change it) by using:
var imageView : UIImageView! = sender.view! as UIImageView
What I need to do now, however, is access the cell's data, which I believe means I need to be able to access the cell in order to get the section and row number. Can anyone advise on how to do this? The idea is that I am changing the image to an unchecked checkbox if the task is done, but then in my data I need to actually mark that task as done.
In your function that handles the tap gesture recognizer, use tableView's indexPathForRowAtPoint function to obtain and optional index path of your touch event like so:
func handleTap(sender: UITapGestureRecognizer) {
let touch = sender.locationInView(tableView)
if let indexPath = tableView.indexPathForRowAtPoint(touch) {
// Access the image or the cell at this index path
}
}
From here, you can call cellForRowAtIndexPath to get the cell or any of its content, as you now have the index path of the tapped cell.
You can get the position of the image tapped in order to find the indexPath and from there find the cell that has that image:
var position: CGPoint = sender.locationInView(self.tableView)
var indexPath: NSIndexPath = self.tableView.indexPathForRowAtPoint(position)!
var cell = self.tableView(tableView, cellForRowAtIndexPath: indexPath)
You're missing that you can use the Delegate design pattern here.
Create a protocol that tells delegates the state changes in your checkbox (imageView):
enum CheckboxState: Int {
case .Checked = 1
case .Unchecked = 0
}
protocol MyTableViewCellDelegate {
func tableViewCell(tableViewCell: MyTableViewCell, didChangeCheckboxToState state: CheckboxState)
}
And assuming you have a UITableViewCell subclass:
class MyTableViewCell: UITableViewCell {
var delegate: MyTableViewCellDelegate?
func viewDidLoad() {
// Other setup here...
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: "didTapImage:")
imageView.addGestureRecognizer(tapGestureRecognizer)
imageView.userInteractionEnabled = true
}
func didTapImage(sender: AnyObject) {
// Set checked/unchecked state here
var newState: CheckboxState = .Checked // depends on whether the image shows checked or unchecked
// You pass in self as the tableViewCell so we can access it in the delegate method
delegate?.tableViewCell(self, didChangeCheckboxToState: newState)
}
}
And in your UITableViewController subclass:
class MyTableViewController: UITableViewController, MyTableViewCellDelegate {
// Other methods here (viewDidLoad, viewDidAppear, etc)...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) {
var cell = tableView.dequeueReusableCellWithIdentifier("YourIdentifier", forIndexPath: indexPath) as MyTableViewCell
// Other cell setup here...
// Assign this view controller as our MyTableViewCellDelegate
cell.delegate = self
return cell
}
override func tableViewCell(tableViewCell: MyTableViewCell, didChangeCheckboxToState state: CheckboxState) {
// You can now access your table view cell here as tableViewCell
}
}
Hope this helps!
Get tableView's cell location on a specific cell. call a function which holds the gesture of cell's Objects Like UIImage or etc.
let location = gesture.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: location)
Print(indexPath.row)
Print(indexPath.section)

UILongPressGestureRecognizer does not get deallocated from CollectionViewCell instance

Ok so I'm out of ideas
Here is my custom UICollectionViewCell class:
class baseCViewCell: UICollectionViewCell {
var mainCV : AnyObject!
var indexPath : NSIndexPath!
class func getIdentifier() -> String {
return NSStringFromClass(self).componentsSeparatedByString(".").last!
}
var editGesture : UILongPressGestureRecognizer!
func initialize(parent:AnyObject, indexPath:NSIndexPath) {
mainCV = parent as baseCView
self.indexPath = indexPath
editGesture = UILongPressGestureRecognizer(target: self, action: Selector("edit:"))
editGesture.minimumPressDuration = 1.0
editGesture.allowableMovement = 100.0
self.addGestureRecognizer(editGesture)
}
func edit(gesture: UILongPressGestureRecognizer) {
if (gesture == editGesture) {
if (gesture.state == UIGestureRecognizerState.Began) {
testinglog("Edit pressed on (\(indexPath.row) in \(indexPath.section))")
}
}
}
deinit {
self.removeGestureRecognizer(editGesture)
}
}
My question is .. Why do the gesture objects not dealloc after the cell becomes not-visible (scrolling).
I have tried all I know to force them to dealloc .. with no success
This is how I use the cell:
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 100;
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
var cell = collectionView.dequeueReusableCellWithReuseIdentifier("CViewCell", forIndexPath: indexPath) as CViewCell
cell.initialize(collectionView, indexPath: indexPath)
return cell
}
Again .. If I scroll up and down I can see in Instruments the memory count goes up .. and up .. and only up .. being equivalent to a leak .. only not detected.
I commented the part where I add the gestureRecognizer and all is ok (just mentioned it so I don't have to answer if I am sure if the problem is there).
Collection views reuse cells. As views move offscreen, they are removed from view and placed in a reuse queue instead of being deleted. So, the deinitializer of cell probably will not be called until the collection view is deallocated.
What you are current doing now is adding a new gesture recognizer to the cell every time you dequeue it, this increases memory usage. You should create and initialize the gesture recogniser in the initializer of the cell.
By the way, your cell holds a strong reference back to the collection view, which creates a strong reference cycle, that's not a good thing.

Resources