Using NotificationCenter Observer to Handle Asynch

2019-08-04 06:40发布

Similar questions to this have been asked so I apologize, but none of them have been able to help me.

I am struggling to return the value from this asynchronous request to Firebase with a completion handler. The value I am retrieving from Firebase is an array and it does exist. But

Here is my function for making the request to Firebase:

class SearchManager {

    var searchResults = [String]()
    var listOfMosaics = [String]()

    // Retrieves company list from Firebase
    func getMosaicTitles(completionHandler: @escaping (_ mosaics: [String]) -> ()) {
        Database.database().reference().child("mosaics").observeSingleEvent(of: .value, with: { (snapshot) in
            guard let allMosaics = snapshot.value as? [String] else {
                print("unable to unwrapp datasnapshot")
                return
            }
            completionHandler(allMosaics)
        })
    }

    // resets search results array
    func resetSearch() {
        searchResults = []
    }

    // takes list of all mosaics and filters based on search text
    func filterAllMosaics(searchText: String) {
        searchResults = listOfMosaics.filter { $0.contains(searchText) }

    }

}

And in the AppDelegate I call it like this posting a Notification:

    let searchManager = SearchManager()

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    makeRootViewLaunchScreen()
    FirebaseApp.configure()
    searchManager.getMosaicTitles { (results) in
        self.searchManager.listOfMosaics = results
        NotificationCenter.default.post(name: NSNotification.Name("mosaicsReturned"), object: nil)
        self.stopDisplayingLaunchScreen()
    }
    // Adds border to bottom of the nav bar
    UINavigationBar.appearance().shadowImage = UIImage.imageWithColor(color: UIColor(red:0.00, green:0.87, blue:0.39, alpha:1.0))
    // Override point for customization after application launch.
    return true
}

func makeRootViewLaunchScreen() {
    let mainStoryboard: UIStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
    let viewController = mainStoryboard.instantiateViewController(withIdentifier: "launchScreen")
    UIApplication.shared.keyWindow?.rootViewController = viewController
}

// reassigns root view after Firebase request complete
func stopDisplayingLaunchScreen() {
    let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController")
    UIApplication.shared.keyWindow?.rootViewController = viewController
}

In the viewDidLoad of the viewController that supports the tableView that uses the retrieved array to populate it I add a Notification Observer.

    var listOfMosaics = [String]()
var searchResults = [String]() {
    didSet {
        tableView.reloadData()
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    listOfMosaics = searchManager.listOfMosaics
    configureSearchBar()
    configureSearchBarTextField()
    self.tableView.separatorColor = UIColor(red:0.00, green:0.87, blue:0.39, alpha:1.0)

    NotificationCenter.default.addObserver(self, selector: #selector(updateListOfMosaics), name: NSNotification.Name("mosaicsReturned"), object: nil)
}

@objc func updateListOfMosaics(notification: Notification) {
    listOfMosaics = searchManager.listOfMosaics
}

But when I call the below code it doesn't work the arrays print as empty and as a result it doesn't update my tableView.

extension SearchResultsTableViewController: UISearchBarDelegate {

func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    searchManager.resetSearch()
    searchManager.filterAllMosaics(searchText: searchBar.text!)
    tableView.reloadData()
    print(listOfMosaics)
    print(searchResults)


   }
 }

Thank you in advanced for the help.

3条回答
聊天终结者
2楼-- · 2019-08-04 07:02

As @TNguyen says in his comment, it sounds like you aren't waiting for the async function getMosaicTitles() to complete.

You might want to disable the search bar button while the call is running, and enable it from the completion handler once the call is complete. Then the user won't be able to click the search button until the results have finished loading.

查看更多
\"骚年 ilove
3楼-- · 2019-08-04 07:13

You can fetch the data from the database in a background thread and add a completion block, so that the tableView reloads only after the updated content is fetched.

查看更多
▲ chillily
4楼-- · 2019-08-04 07:15

This should work for you now. I think you didn't pass the instance of SearchManager from your AppDelegate to your ViewController. I'm guessing you created a new instance of SearchManager in your ViewController, which has an empty array.

Search Manager:

class SearchManager {

    var searchResults = [String]()
    var listOfMosaics = [String]()

    func getMosaicTitles(completionHandler: @escaping (_ mosaics: [String]) -> ()) {
        Database.database().reference().child("mosaics").observeSingleEvent(of: .value, with: { (snapshot) in
            guard let allMosaics = snapshot.value as? [String] else {
                print("unable to unwrapp datasnapshot")
                completionHandler([]) // <- You should include this too.
                return
            }
            completionHandler(allMosaics)
        })
    }

    func resetSearch() {
        searchResults = []
    }

    func filterAllMosaics(searchText: String) {
        searchResults = listOfMosaics.filter { $0.contains(searchText) }
    }
}

View Controller:

class TableViewController: UITableViewController {

    var searchManager: SearchManager?
    var listOfMosaics = [String]()
    var searchResults = [String]() {
        didSet {
            tableView.reloadData()
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let searchManager = searchManager else { return }
        listOfMosaics = searchManager.listOfMosaics
        print("List of mosaics: \(listOfMosaics)")
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 0
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 0
    }
}

AppDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let searchManager = SearchManager()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?)  -> Bool {
        makeRootViewLaunchScreen()
        FirebaseApp.configure()
        searchManager.getMosaicTitles { results in
            self.searchManager.listOfMosaics = results
            self.stopDisplayingLaunchScreen()
        }
        return true
    }

    func makeRootViewLaunchScreen() {
        let mainStoryboard: UIStoryboard = UIStoryboard(name: "LaunchScreen", bundle: nil)
        let viewController = mainStoryboard.instantiateViewController(withIdentifier: "launchScreen")
        window?.rootViewController = viewController
        window?.makeKeyAndVisible()
    }

    func stopDisplayingLaunchScreen() {
        let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
        guard let viewController = mainStoryboard.instantiateViewController(withIdentifier: "centralViewController") as? TableViewController else { return }
        let navigationController = UINavigationController(rootViewController: viewController)
        viewController.searchManager = searchManager
        window?.rootViewController = navigationController
        window?.makeKeyAndVisible()
    }
}
查看更多
登录 后发表回答