collectionView :didSelectItemAtIndexPath called with a delay - ios

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

Related

ios: disable second touch event while tapping on collectionview cell or tableview cell

I have a ViewController with a CollectionView that loads four possible answers to a question asked in the ViewController.
When the user selects an item, the collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) gets called in which the question changes along with the array of answers (always 4) then a collectionView.reloadData is called to redraw the new question and possible answers.
Everything is working perfectly, except for when the user taps on an item quickly 2 times in a row.
in that case, the first selection registers the answer, and then the collectionview is taking another tap (as if the user has tapped again on the next question) and thus answering another question.
What i would like to do, if possible, is the following:
1. disable touch events on the first touch (while reloading a new question)
2. re-enable touch events once the reloadData of the collectionView has finished loading. Which is another problem that i solved using a custom Collection View Class taken from this thread How to tell when UITableView has completed ReloadData?
I have tried disabling touch events using: view.userInteractionEnabled = false/true and UIApplication.shared.beginIgnoringInteractionEvents() and UIApplication.shared.endIgnoringInteractionEvents() with no luck.
Here is what I've tried so far:
func loadNewQuestion {
//UIApplication.shared.beginIgnoringInteractionEvents()
//self.view.isUserInteractionEnabled = false
//change the question, answer, and array of possible answers
answers = answers.shuffled() //simply shuffle the answers
//pick a random answer
let number = Int.random(in: 0 ... 3)
answer = answers[number] //shuffle again and take first value
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if stillAnswering {
print("still answering, so skipping touch events")
return
}
stillAnswering = true
print("not answering")
let a = answers[indexPath.row]
if a.descript.lowercased() == questionAnswer.descript.lowercased() //questionAnswer is the correct answer to the question {
self.loadNewQuestion()
self.collectionView.reloadData(onComplete: {
//UIApplication.shared.endIgnoringInteractionEvents()
//self.view.isUserInteractionEnabled = true
stillAnswering = false
})
} else {
//warn about wrong answer
stillAnswering = false
}
}
I have tagged both objective-c and swift because i don't mind the language used for the solution, and also i believe that the solution/problem is similar for uitableview vs uicollectionview.
Any Hints?
You can use flag and set it to true when you make a tap on the correct answer cell, then set it to false in the onComplete block of reloadData().
Ex:
var answerChosen: Bool = false
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if answerChosen { return }
let a = answers[indexPath.row]
if a.descript.lowercased() == questionAnswer.descript.lowercased() //questionAnswer is the correct answer to the question {
answerChosen = true
self.loadNewQuestion()
self.collectionView.reloadData(onComplete: {
answerChosen = false
})
} else {
//warn about wrong answer
}
}
I have finally managed to solve the problem. The trick that solved it is to put the reloadData() inside a dispatch async block.
Here's the final code.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if UIApplication.shared.isIgnoringInteractionEvents {
return //for extra safety
}
UIApplication.shared.beginIgnoringInteractionEvents()
let a = answers[indexPath.row]
if a.descript.lowercased() == questionAnswer.descript.lowercased() //questionAnswer is the correct answer to the question {
self.loadNewQuestion()
DispatchQueue.main.async(execute: {
self.collectionView.reloadData(onComplete: {
UIApplication.shared.endIgnoringInteractionEvents()
})
})
} else {
//warn about wrong answer
DispatchQueue.main.async(execute: {
self.collectionView.reloadData(onComplete: {
UIApplication.shared.endIgnoringInteractionEvents()
})
})
}
}

CollectionView Showing Old Data

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 !

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

Navigation like in the mail app with segues and dynamic cells in a table view?

I'm currently developing my first iOS app for a college. When you start the app, there is a grouped table view which should represent a menu. This works fine so far, but I'm unable to connect the segues properly. As far as I know I should connect the cell itself with the navigation controller it should lead to. Although every cell should lead to another navigation controller. It's a menu, so that point should be clear. Unfortunately I'm unable to connect a single cell with multiple navigation controllers and when I add multiple prototype cells, I have the problem that every cell should have it's own identifier.
The first screen you see in my app looks basically like the mail app. There are two groups and each cell leads to another navigation controller.
I managed to implement the navigation in some weird way, but get a lot of bugs like "nested push results in corrupted nav bar".
I'm absolutely frustrated right now, I spent a lot of hours on this single problem already and I'm unable to find any solution.
edit:
one of the biggest problems currently is that if I navigate to a point and then head back the the view before, the grouped view is first displayed too close to the top and when the animation is complete the whole view jumps back down where it belongs.
this is how it should look like (and looks like when it jumps back): https://dl.dropboxusercontent.com/u/34140308/uploads/2016-04-25%2010.28.48.png
this is how it looks like during the animation: https://dl.dropboxusercontent.com/u/34140308/uploads/2016-04-25%2010.28.48%202.png
edit2: I connected the view controller itself (not the cells) with the respective view controller. Then I call the segues programmatically like this:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
if indexPath.section == 0 {
if indexPath.row == 0 {
self.performSegueWithIdentifier("showSpeiseplan", sender: self)
} else if indexPath.row == 1 {
self.performSegueWithIdentifier("showStudiersaal", sender: self)
} else if indexPath.row == 2 {
self.performSegueWithIdentifier("showErzieher", sender: self)
} else if indexPath.row == 3 {
self.performSegueWithIdentifier("showHausordnung", sender: self)
} else if indexPath.row == 4 {
self.performSegueWithIdentifier("showNews", sender: self)
}
} else if indexPath.section == 1 {
if indexPath.row == 0 {
self.performSegueWithIdentifier("showZugewieseneStunden", sender: self)
}
}
}
The other major problem right now is that if you press a single cell multiple times before it's loaded, the view gets opened multiple times as well.
The way I usually do segue's from a tableView is by connecting the segue from the viewController rather than the tableviewCells. This way you can call performSegueWithIdentifier in didSelectRowAtIndexPath.
Eg
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
tableView.userInteractionEnabled = false
switch indexPath.section {
case 0:
switch indexPath.row {
case 0: performSegueWithIdentifier("showSpeiseplan", sender: self)
case 1: performSegueWithIdentifier("showStudiersaal", sender self)
case 2: performSegueWithIdentifier("showErzieher", sender self)
case 3: performSegueWithIdentifier("showHausordnung", sender self)
case 4: performSegueWithIdentifier("showNews", sender self)
default: tableView.userInteractionEnabled = true
}
case 1:
switch indexPath.row {
case 0: performSegueWithIdentifier("showZugewieseneStunden", sender: self)
default: tableView.userInteractionEnabled = true
}
default: tableView.userInteractionEnabled = true
}
}
and in viewWillDisappear:
override func viewDidDisappear(animated: Bool) {
tableView.userInteractionEnabled = true
}
If your cells are editable or dynamic (they change indexPath) then either use cell.tag in cellForRowAtIndexPath to distinguish them, or add a property to you custom cell class (or make one if you haven't) and add a property that you can use to identify the correct segue.
Edit: To fix the tableView Offset jump on animation, create a variable tableViewContentOffset above viewDidLoad, and then in viewDidLoad add:
tableViewContentOffset = tableView.contentOffset
and in viewWillAppear restore it if it has changed.
if tableView.contentOffset != tableViewContentOffset {
tableView.contentOffset = tableViewContentOffset
}
also, maybe just see what happens when you put this in viewWillAppear:
tableView.contentOffset = CGPointZero
tableView.contentInset = UIEdgeInsetsZero

iOS 8 - performSegueWithIdentifier loads view but doesn't show until I tap the screen

I have a Tab Bar application, and one of the tabs, which contains a Table View, segues into a third view when a table cell is pressed. The view controller acts as a delegate for the UITableView, and I trigger the segue programatically as follows:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("cell tapped, starting segue")
performSegueWithIdentifier("showDetails", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
println("prep for segue")
// TODO - more code here
}
Finally, I set up the following code to debug the problem with the third view:
class DetailsViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
println("did load")
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
println("will appear")
}
}
The problem is that when I press a table cell for the first time, the viewWillAppear function never gets called until I interact with the UI in some way (e.g. just a tap anywhere on the screen). The view that I want to segue into doesn't show up, as if the screen didn't get refreshed. However, when I tap the screen, the whole animation runs and I can segue as intended. This is my output when I tap a cell:
cell tapped, starting segue
prep for segue
did load
I tried to find solutions online, but all the issues I found it seems to just not work at all. In my case, it is working, but not immediately.
In case it helps, here's a screenshot of my storyboard:
Sefu found the answer and posted it in the comments, I ran into the same issue and his solution worked for me. The trick is to make it so the cell that is selected that triggers the segue needs to have a selection style set (not None), and I also found that deselecting the cell in tableView:didSelectRowAtIndexPath: also needed to happen.
Ran into a similar problem while having my selectionStyle = .None .
An option you can use, if you're like me and don't want a selectionStyle applied is to set the cell item back to unselected in the prep for segue.
That seemed to stopped the 'issue' I was seeing where the segue would work perfectly once, but all subsequent calls would require selecting the cell twice.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//sending the index path up as the sender so the prep for segue can access the cell
self.performSegueWithIdentifier("segueID", sender: indexPath);
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "segueID"){
if let indexPath : NSIndexPath = sender as? NSIndexPath{
tableViewReference.cellForRowAtIndexPath(indexPath)?.selected = false;
let destinationVC : UIViewControllerClass = segue.destinationViewController as! UIViewControllerClass;
destinationVC.customMethod(/* some value */);
}
}
}

Resources