在Swift / iOS中异步更新TableView - 了解NSUrlSession和tableView.reloadData()

时间:2016-04-22 22:49:44

标签: ios swift uitableview asynchronous nsurlsession

我在学习Swift / iOS的过程中很早。我尝试了解异步请求,以及在结果进入时更新tableview的最佳方法。

我正在制作一个简单的电影列表应用并使用' themoviedatabase' API。这是代码,对我来说不起作用。目前它搜索星球大战的力量唤醒了#39;并且工作正常,从API获得预期的单个结果。到目前为止竖起大拇指。

但是当我尝试用我的结果更新tableview时......问题!

以下代码是我在SearchVC.swift自定义视图控制器中创建的函数的一部分。我之前提到过这个我已经研究过的其他帖子(this onethis one)似乎在说你应该只从主线程更新UI - 我是不是已经完全不知道自己是否这样做了?

SearchVC.swift:

import UIKit

class SearchVC: UIViewController, UITableViewDataSource, UITableViewDelegate {

@IBOutlet weak var tableView: UITableView!
var theResults = [SearchResult]()

@IBOutlet weak var searchField: MaterialTextField!
@IBOutlet weak var searchBtn: MaterialButton!

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.delegate = self
    tableView.dataSource = self
    tableView.estimatedRowHeight = 106

    // do I need to update self.theResults[] and do self.tableView.reloadData() in here?

}

@IBAction func searchBtnPressed(sender: MaterialButton) {

    print("Search button pressed!")
    performSearch(searchField.text)

}

func performSearch(searchText: String?) {

    if let searchString = searchText where searchString != "" {

        self.theResults = [] // clear the search results array
        print("Searching for: \(searchString)")
        let tmdb_apikey = "----just gonna take out my API key----"
        let tmdb_query = searchString
        let tmdb_query_escaped = tmdb_query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
        let url = NSURL(string: "http://api.themoviedb.org/3/search/movie?query=\(tmdb_query_escaped)&api_key=\(tmdb_apikey)&page=1")!
        let request = NSMutableURLRequest(URL: url)
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        let session = NSURLSession.sharedSession()
        let task = session.dataTaskWithRequest(request) { data, response, error in
            if let response = response, data = data {
                do {
                    // Convert data to JSON
                    let json = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments)
                    // Convert JSON into a Dictionary
                    if let searchDict = json as? Dictionary<String,AnyObject> {
                        // Iterate through dictionary and add results to the results array
                        if let searchResults = searchDict["results"] as? [Dictionary<String,AnyObject>] {
                            for result in searchResults {
                                if let title = result["title"] as? String, let posterPath = result["poster_path"] as? String, let id = result["id"] as? Int {
                                    let idStr = String(id) // cast id int to string, for storing in our custom class
                                    let thisResult = SearchResult(title: title, posterUrl: posterPath, id: idStr)
                                    print("Results:")
                                    print(thisResult.movieTitle)
                                    self.theResults.append(thisResult) // adds result to array
                                }
                            }
                            print("Num of results to display inside closure: \(self.theResults.count)")
                            // if I put 'self.tableView.reloadData()' here, which is where I thought it should go (once all the results have been added to my array) I get an error: "This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes.  This will cause an exception in a future release." ... and loads of stack dumps!
                        } else {
                            print("ERROR: No 'results'?")
                        }
                    } else {
                        print("ERROR: Got data, converted to JSON, but could not convert into a Dictionary.")
                    }
                } catch {
                    print("ERROR: Could not convert data into JSON")
                }
            } else {
                print(error)
            }
        }
        task.resume()

        // This outputs 0, before results are delivered, because the data request is asynchronous? How do I deal with this?!
        print("Num of results to display: \(theResults.count)")
        self.tableView.reloadData()

    } else {
        // no search term specified
        print("No search term specified. Doing nothing")
    }

}

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return theResults.count
}

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

    let post = theResults[indexPath.row]

    if let cell = tableView.dequeueReusableCellWithIdentifier("SearchResultCell") as? SearchResultCell {

        print("Reconfiguring cell")
        cell.configureSearchCell(post)
        return cell

    } else {
        print("Configuring cell")
        return SearchResultCell()
    }

}

configureSearchCell()在单独的 SearchResultCell.swift 中定义:

import UIKit

class SearchResultCell: UITableViewCell {

    @IBOutlet weak var moviePoster: UIImageView!
    @IBOutlet weak var movieTitle: UILabel!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    func configureSearchCell(post: SearchResult) {

        self.movieTitle.text = post.movieTitle

        if post.moviePosterUrl != "" {
            // doing nothing here for the moment, just want to get the movie title added to a cell before worrying about adding the poster image!
        }

    }

}

performSearch 功能在很大程度上似乎正在起作用,因为它输出以下内容:

Search button pressed!
Searching for: star wars force awakens
Num of results to display: 0
Results:
Star Wars: The Force Awakens
Num of results to display inside closure: 1

所以我得到了结果,但是无法更新我的表,显然是因为调用是异步的,并且在结果仍在处理时调用了第3行输出(并且告诉我在那里有0个结果)点)。我知道了。

但是,当我尝试在我的代码中间调用self.tableView.reloadData()时,在解析结果之后(print()告诉我现在有1个结果),我得到更多上面代码中的注释中提到的错误,警告我通过后台线程更新autolayout!

无论哪种方式,我的tableview中都没有更新任何内容。结果根本没有添加到单元格中。

如何绕过我需要做的事情?

我意识到我可能只是&#39;需要一个关于异步调用的入门知识,以及如何在Swift / xcode中更新表视图,但是到目前为止,很多搜索还没有找到解决这个问题的任何东西,所以我在这里。希望有些知识渊博的人可以对新手iOS程序员表示同情!

提前感谢任何提示。

0 个答案:

没有答案