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?

2 Answers

0
deebee On Best Solutions

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()
            })
        })
    }
}
0
droberson On

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