【问题标题】:Populating table with API response Swift使用 API 响应 Swift 填充表
【发布时间】:2024-01-16 05:51:02
【问题描述】:

我正在尝试让搜索结果显示在 tableView 上。我相信我已经正确解析了 JSON,唯一的问题是结果不会显示在我的 tableView 上。

代码如下:

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
    }

如果我有一个静态 API 请求,我认为这段代码会起作用,因为我可以在 viewDidLoad 中获取并避免大量 .isEmpty 检查。

当我运行程序时,我得到了 6 个 Results not loaded yet(来自我的 print in cellForRowAtIndexPath)。

当完成处理程序被称为response in 时,它会下降到self.items.count > 3(它通过)然后点击self.tableView.reloadData(),它什么都不做(我通过在其上放置断点进行检查)。

我的代码有什么问题?

编辑

 if self.itemsArray.count > 0 {

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

                        })
                    }

试过了,但即使在调用 alamofire 处理程序之前重新加载了 6 次,tableView 仍然没有重新加载...

这是奇怪的事情,很明显在hander 被调用之前我的itemsArray.count 将是0,所以这就是我得到Results not loaded yet 的原因。我弄清楚了为什么它会重复 6 次;我将其设置为numberOfRowsInSection...所以@Rob,我无法检查dict["Text"]cell.resultLabel?.text,因为它们永远不会被调用。 “文本”是正确的,这里是 JSON 的链接:http://api.duckduckgo.com/?q=DuckDuckGo&format=json&pretty=1

另外,我确实将标签链接到自定义单元格类SearchResultCell

最后,我得到了可见的结果。

【问题讨论】:

  • 我会怀疑线程问题,但如果我没记错的话,所有Alamofire.request 完成闭包无论如何都会在主线程上调用,因此除非您以其他方式配置 Alamofire,否则这应该不是问题。 (所有 UI 更改必须在主线程上执行)
  • 是的,这些都发生在主线程上,因此不太可能出现线程问题。更有可能的是(a)单元子类的出口没有正确定义; (b) 字典不包含由Text 键入的值;或 (c) 单元格中的约束使得标签内容的更改不可见。在 Wesley 分享更多调试细节之前,无法进行诊断。
  • 您说“当我运行程序时,我得到 6 个结果‘尚未加载’(来自我在 cellForRowAtIndexPath 中的打印结果)。”嗯,你应该得到那些。该表被加载两次,一次是在第一次加载时,一次是在您执行查询时。第一次你会得到六个“尚未加载”。第二次应该是填充单元格。

标签: ios json swift uitableview alamofire


【解决方案1】:

两个问题。

  1. 一个问题是prepareForSegue:

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

    这不是使用已经实例化的“目标”视图控制器,而是创建第二个SearchResultsTVC,设置其searchText,然后让它超出范围并被释放,在此过程中丢失搜索文本.

    相反,你想要:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let searchResultTVC = segue.destination as? SearchResultsTVC {
            searchResultTVC.searchText = searchField.text
        }
    }
    
  2. 您不应该依赖目标视图控制器中的didSet 来触发搜索,因为源视图控制器甚至在表视图被实例化之前就已经设置了该属性。您不想在视图加载之前启动搜索 (viewDidLoad)。

    我建议替换 didSet 逻辑并在 viewDidLoad 中搜索 SearchResultsTVC

我的原始答案,讨论原始问题中提供的代码如下。

--

我使用了问题中最初提供的代码,它运行良好。就个人而言,我可能会进一步简化它:

  • 消除numberOfRowsInSection 中硬编码的“6”,因为这会导致控制台出现误报;

  • 转义百分比不太正确(某些字符会滑过,未转义);与其纠结于自己做这件事的正确方法,不如让 Alamofire 为你做这件事,使用parameters

  • 我个人会删除 SwiftyJSON,因为它没有提供任何价值……Alamofire 已经为我们完成了 JSON 解析。

无论如何,我的简化版本如下所示:

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
    }

}

当我这样做时,我得到了以下信息:

问题必须在其他地方解决。也许它在故事板中。也许它在更新searchText 的代码中您没有与我们共享(这会通过didSet 触发查询)。很难说。但在你提供的代码 sn-p 中似乎没有问题。

但是在进行调试时,请确保不要将第一次调用表视图委托方法和第二次调用它们混为一谈,因为它们是由 responseJSON 块触发的。通过消除numberOfRowsInSection 中的硬编码“6”,这将减少一些误报。

【讨论】:

  • 我想我需要添加我正在使用 textField 进行搜索的人脸,因此我无法将提取放入 viewDidLoad。这是我的完整代码github.com/charleswcho/DDG-Search
  • @WesleyCho 请参阅上面我修改后的答案。两个问题: 1. 你的prepareForSegue 没有正确更新目标视图控制器; 2.你应该在目的地设置searchText,然后让viewDidLoad进行搜索;在视图控制器之间转换时不要使用didSet,因为您甚至在目标表视图被实例化之前就调用了更新该属性。
  • 全面讲解,感谢您的努力!
【解决方案2】:

我认为你应该编辑:

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

还有

    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
}

你应该分享json结果(格式),在cellForRowAtIndexPath中打印dict,这样很容易得到帮助

【讨论】: