4

I'm trying to get search results to display on a tableView. I believe I have correctly parsed the JSON, the only problem is that the results won't display on my tableView.

Here is the code:

var searchText : String! {
        didSet {
            getSearchResults(searchText)
        }
    }

    var itemsArray = [[String:AnyObject]]()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.delegate = self
        self.tableView.dataSource = self

        self.tableView.reloadData()

    }

    // MARK: - Get data

    func getSearchResults(text: String) {

        if let excapedText = text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {

            Alamofire.request(.GET, "https://api.duckduckgo.com/?q=\(excapedText)&format=json")
                .responseJSON { response in
                    guard response.result.error == nil else {

                        // got an error in getting the data, need to handle it
                        print("error \(response.result.error!)")
                        return

                    }

                    let items = JSON(response.result.value!)

                    if let relatedTopics = items["RelatedTopics"].arrayObject {

                        self.itemsArray = relatedTopics as! [[String:AnyObject]]
                    }

                    if self.itemsArray.count > 0 {

                        self.tableView.reloadData()
                    }

            }

        }
    }


    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

        return 1
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return 6 // itemsArray.count
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell", forIndexPath: indexPath) as! SearchResultCell

        if itemsArray.count > 0 {

            var dict = itemsArray[indexPath.row]
            cell.resultLabel?.text = dict["Text"] as? String

        } else {

            print("Results not loaded yet")

        }

        return cell
    }

If I had a static API request I think this code would work because I could fetch in the viewDidLoad and avoid a lot of the .isEmpty checks.

When I run the program I get 6 Results not loaded yet (from my print in cellForRowAtIndexPath).

When the completion handler is called response in, it goes down to self.items.count > 3 (which passes) then hits self.tableView.reloadData() which does nothing (I checked by putting a breakpoint on it).

What is the problem with my code?

Edit

 if self.itemsArray.count > 0 {

                        dispatch_async(dispatch_get_main_queue(), { () -> Void in
                            self.tableView.reloadData()

                        })
                    }

Tried this but the tableView still did not reload even though its reloading 6 times before the alamofire hander is called...

Here is the strange thing, obviously before the hander is called my itemsArray.count is going to be 0 so that's why I get Results not loaded yet. I figured out why it repeats 6 times though; I set it in numberOfRowsInSection... So @Rob, I can't check dict["Text"] or cell.resultLabel?.text because they're never getting called. "Text" is correct though, here is the link to the JSON: http://api.duckduckgo.com/?q=DuckDuckGo&format=json&pretty=1

enter image description here

Also, I do have the label linked up to a custom cell class SearchResultCell

enter image description here enter image description here

Lastly, I am getting visible results.

enter image description here

3
  • I would suspect threading issues, but if I remember correctly, all Alamofire.request completion closures are called on the main thread anyway, so that shouldn't be a problem unless you are configuring Alamofire otherwise. (All UI changes must be performed on the main thread) Commented Nov 13, 2015 at 2:16
  • Yes, those all happen on the main thread, so threading issues are unlikely. It's far more likely that either (a) the outlets to the cell subclass were not defined properly; (b) the dictionary doesn't contain a value keyed by Text; or (c) the constraints in the cell are such that the changing of the label contents are not visible. It's impossible to diagnose until Wesley shares more debugging details. Commented Nov 13, 2015 at 4:33
  • You say "When I run the program I get 6 Results 'not loaded yet' (from my print in cellForRowAtIndexPath)." Well, you should get those. The table is loaded twice, once when first loaded, and again when you perform the query. The first time around you will get six "not loaded yet". The second time around it should be populating the cells. Commented Nov 13, 2015 at 4:57

2 Answers 2

6

Two problems.

  1. One issue is prepareForSegue:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        let searchResultTVC = SearchResultsTVC()
        searchResultTVC.searchText = searchField.text
    }
    

    That's not using the "destination" view controller that was already instantiated, but rather creating a second SearchResultsTVC, setting its searchText and then letting it fall out of scope and be deallocated, losing the search text in the process.

    Instead, you want:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let searchResultTVC = segue.destination as? SearchResultsTVC {
            searchResultTVC.searchText = searchField.text
        }
    }
    
  2. You shouldn't rely on didSet in the destination view controller to trigger the search, because that property is getting set by source view controller before the table view has even been instantiated. You do not want to initiate the search until view has loaded (viewDidLoad).

    I would advise replacing the didSet logic and just perform search in viewDidLoad of that SearchResultsTVC.

My original answer, discussing the code provided in the original question is below.

--

I used the code originally provided in the question and it worked fine. Personally, I might streamline it further:

  • eliminate the rid of the hard coded "6" in numberOfRowsInSection, because that's going to give you false positive errors in the console;

  • the percent escaping not quite right (certain characters are going to slip past, unescaped); rather than dwelling on the correct way to do this yourself, it's better to just let Alamofire do that for you, using parameters;

  • I'd personally eliminate SwiftyJSON as it's not offering any value ... Alamofire already did the JSON parsing for us.

Anyway, my simplified rendition looks like:

class ViewController: UITableViewController {

    var searchText : String!

    override func viewDidLoad() {
        super.viewDidLoad()

        getSearchResults("DuckDuckGo")
    }

    var itemsArray: [[String:AnyObject]]?

    func getSearchResults(text: String) {
        let parameters = ["q": text, "format" : "json"]
        Alamofire.request("https://api.duckduckgo.com/", parameters: parameters)
            .responseJSON { response in
                guard response.result.error == nil else {
                    print("error \(response.result.error!)")
                    return
                }

                self.itemsArray = response.result.value?["RelatedTopics"] as? [[String:AnyObject]]
                self.tableView.reloadData()
        }
    }

    // MARK: - Table view data source

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "SearchResultCell", for: indexPath) as! SearchResultCell

        let dict = itemsArray?[indexPath.row]
        cell.resultLabel?.text = dict?["Text"] as? String

        return cell
    }

}

When I did that, I got the following:

enter image description here

The problem must rest elsewhere. Perhaps it's in the storyboard. Perhaps it's in the code in which searchText is updated that you didn't share with us (which triggers the query via didSet). It's hard to say. But it doesn't appear to be a problem in the code snippet you provided.

But when doing your debugging, make sure you don't conflate the first time the table view delegate methods are called and the second time they are, as triggered by the responseJSON block. By eliminating the hardcoded "6" in numberOfRowsInSection, that will reduce some of those false positives.

Sign up to request clarification or add additional context in comments.

3 Comments

I guess I needed to add the face that I'm using a textField to do the search so I can't put the fetch in viewDidLoad. Here is my full code github.com/charleswcho/DDG-Search
@WesleyCho See my revised answer above. Two problems: 1. your prepareForSegue wasn't updating the destination view controller properly; 2. You should just set searchText in the destination and then let viewDidLoad perform the search; don't use didSet when transitioning between view controllers, because you're calling updating that property before the destination table view has even been instantiated.
Comprehensive explanation, thank you for your effort!
0

I think you should edit :

func getSearchResults(text: String) {

    if let excapedText = text.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {

        Alamofire.request(.GET, "https://api.duckduckgo.com/?q=\(excapedText)&format=json")
            .responseJSON { response in
                guard response.result.error == nil else {

                    // got an error in getting the data, need to handle it
                    print("error \(response.result.error!)")
                    return

                }
                let items = JSON(response.result.value!)
                if let relatedTopics = items["RelatedTopics"].arrayObject {
                    self.itemsArray = relatedTopics as! [[String:AnyObject]]
                    // if have result data -> reload , & no if no
                    if self.itemsArray.count > 0 {
                        self.tableView.reloadData()
                    }
                }else{
                    print("Results not loaded yet")
                }
        }
    }
}

And

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell", forIndexPath: indexPath) as! SearchResultCell
    // i 'm sure: itemsArray.count > 0 in here if in numberOfRowsInSection return itemsArray.count
    var dict = itemsArray[indexPath.row]
    cell.resultLabel?.text = dict["Text"] as? String
    return cell
}

And you should share json result(format) ,print dict in cellForRowAtIndexPath, so it s easy for help

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.