Search code examples
iosswiftcore-datansoperationqueueuiapplication

UIApplication's `beginIgnoringInteractionEvents` not working


First of all my apology for long question, i personally prefer short precise question.

Background

i am developing a search feature within a UITableView. To Store data i'm using core data. To show the results i'm using NSFetchedResultsController. In my view model i have a separate NSFetchedResultsController to denote the search result.

Currently i'm using following method

`textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String)` 

from UITextFieldDelegate to listen the changes in my UITextField and based on that i initiate the search.

My perform fetch portion look like this

//
// ALL of this operation Run on MainQueue
//
// Preparing NSFetchRequest and setting 
// it's predicate Goes here 
// 

UIApplication.shared.beginIgnoringInteractionEvents()

managedObjectContext?.perform {
        do {
            try self.allContactSearchFetchResultController?.performFetch()
            DispatchQueue.main.async {
                UIApplication.shared.endIgnoringInteractionEvents()
                // calling the delegate which will reload the UITableView
            }

        } catch {
            print("Error occur")
            // TODO 
            // handle error here
        }
    }

I have stopping User interaction so that, user cannot type in keyboard anymore until this search ends. If multiple search initiated, the tableview reloads indefinitely and the application crashed while accessing following

managedObject = allContactSearchFetchResultController?.object(at: indexPath)

From UITableViewDataSource

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

To run one search operation at a time i have start ignoring user interaction. After the search ends i end ignoring user interaction.

Problem

It works fine until i notice pressing & holding the backspace button, the begin ignoring user interaction is not working.

enter image description here

So the question arise how did i check that the begin ignoring user interaction is not working. I put two breakpoint

one at

UIApplication.shared.beginIgnoringInteractionEvents()

another at

UIApplication.shared.endIgnoringInteractionEvents()

when i press and hold the backspace button multiple breakpoint hit the beginIgnoringInteractionEvents without hitting endIgnoringInteractionEvents.

Probable solution:

Already i have found a way around by using managedObjectContext?.performAndWait instead of managedObjectContext?.perform. But this stuck the main thread. I have to show in the UILabel that current status is searching.... If i use managedObjectContext?.performAndWait it omit the stage searching and directly shows the search result after a while. Instead of this i prefer ignore the User interaction and showing the current status of the search operation.

Any help will be appreciated. T.I.A


Solution

  • As i wanted to stop

    tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
    

    while i prepare the search result. The result was taken from allContactSearchFetchResultController but i assign it to a new NSFetchedResultsController but the UITableView was calling for managedObject based on the old section count and row count in allContactSearchFetchResultController as follows

    Section

    allContactSearchFetchResultController?.sections?.count
    

    Row

    allContactSearchFetchResultController?.sections[section].numberOfObject // There are more guard involved in this number of row fetching, for simplicity purpose i omitted those.
    

    But when i assign allContactSearchFetchResultController to a new NSFetchedResultsController, table view keep asking for old managed object at IndexPath from cellForRowAt because the table view has no idea that i have assigned it to new NSFetchedResultsController. So in this scenario my solution is to create a new local NSFetchedResultsController prepare it, perform fetch on it on managedObjectContext's queue. While the results are ready switch to the mainQueue and assign it. Mine perform fetch portion now looks like this.

    //
    // ALL of this operation Run on MainQueue
    //
    let allFetchRequest = prepareCommonFetchRequest()
        allFetchRequest.predicate = NSPredicate.init(format: "name CONTAINS[cd] %@ || mobile_number CONTAINS[cd] %@", searchString, searchString)
    
        let tempAllContactSearchFetchResultController = NSFetchedResultsController.init(fetchRequest: allFetchRequest,
                                                                                managedObjectContext: managedObjectContext!,
                                                                                sectionNameKeyPath: "section_key",
                                                                                cacheName: nil)
    
    managedObjectContext?.perform {
        do {
            try self.allContactSearchFetchResultController?.performFetch()
            DispatchQueue.main.async {
                self.allContactSearchFetchResultController = tempAllContactSearchFetchResultController
                // calling the delegate which will reload the UITableView
            }
    
        } catch {
            print("Error occur")
            // TODO 
            // handle error here
        }
    }