1

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?

Dany Balian
  • 608
  • 7
  • 16
  • loadNewQuestion simply shuffles the existing array and picks another random answer.. i updated the question anyways – Dany Balian Apr 24 '19 at 14:44

2 Answers2

0

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
    }
}
droberson
  • 43
  • 3
  • it doesn't work, and i had already tried this approach. I updated my question to reflect your changes, what i am getting is 2 printed texts: "not answering". i believe the problem is not about skipping a touch event.. because what is happening is between the first touch and the second touch, the ui is refreshing the collectionview so the OS is saving the second touch until the ui refreshes and sending the touch to whichever item is appearing at the same location. – Dany Balian Apr 24 '19 at 14:57
  • It looks like you're missing the line where you set stillAnswering = true. Try that right under the second if statement. – droberson Apr 24 '19 at 17:27
  • sorry my bad, forgot to paste that part.. editing the question back (actually i put it right after the first if statement, but that doesn't matter – Dany Balian Apr 24 '19 at 21:25
0

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()
            })
        })
    }
}
Dany Balian
  • 608
  • 7
  • 16