swiftuitableviewrx-swiftrxalamofire

Bind Alamofire request to table view using RxSwift


So I have been researching RxSwift for a couple days, and am trying to create a simple app with it. I have bound the searchController of my table to the results, which feed into the cellForRowAt function. How do I bind the alamofire response to each cell? Which of these do I need to do?

The function I need to use is:

.bind(to: self.tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) {  row, element, cell in
    cell.textLabel?.text = "something"
}

This is my current RxSwift code:

let disposeBag = DisposeBag()
var searchResultsArray = [[String:String]]()  
searchController.searchBar.rx.text.orEmpty.filter { text in
        text.count >= 3
    }.subscribe(onNext: { text in
        searchRequest(search: text, searchType: "t:t") { response in
        self.searchResultsArray = response
        self.tableView.reloadData()
        }
    }).disposed(by: disposeBag)

This is my current cell creation function. showSearchResults changes when the cancel button is clicked.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell = {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") else {
            return UITableViewCell(style: .subtitle, reuseIdentifier: "cell")
        }
        return cell
    }()
    if self.shouldShowSearchResults {
        cell.textLabel?.text = searchResultsArray[indexPath.row]["result"]!
        cell.detailTextLabel?.text = searchResultsArray[indexPath.row]["location"]!
    }

    return cell
}

This is my current api request:

func searchRequest(search: String, searchType: String, completionHandler: @escaping ([[String: String]]) -> ()) {
    let payload: [String: Any] = [
        "q": search,
        "fq": searchType,
        "start": 0
    ]

    let url = URL(string: "https://www.athletic.net/Search.aspx/runSearch")!
    Alamofire.request(url, method: .post, parameters: payload, encoding: JSONEncoding.default).responseJSON { response in
        let json = response.data
        do {
            var searchResults: [[String: String]] = []
            let parsedJson = JSON(json!)
            if let doc = try? Kanna.HTML(html: parsedJson["d"]["results"].stringValue, encoding: .utf8) {
                for row in doc.css("td:nth-child(2)") {
                    let link = row.at_css("a.result-title-tf")!
                    let location = row.at_css("a[target=_blank]")!
                    let schoolID = link["href"]!.components(separatedBy: "=")[1]
                    searchResults.append(["location": location.text!, "result": link.text!, "id":schoolID])
                }
            }
            completionHandler(searchResults)
        } catch let error {
            print(error)
        }
    }
}

I would like to replace the cellForRowAt with a RxSwift solution.


Solution

  • Based on the code you presented, use of Rx will give you something like this:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        searchController.searchBar.rx.text.orEmpty
            .filter { text in text.count >= 3 }
            .flatMapLatest { text in searchRequest(search: text, searchType: "t:t") }
            .bind(to: self.tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) {  row, element, cell in
                if self.shouldShowSearchResults {
                    cell.textLabel?.text = element["result"]!
                    cell.detailTextLabel?.text = element["location"]!
                }
            }
            .disposed(by: disposeBag)
    }
    

    The shouldShowSearchResults feels out of place in that. But otherwise it looks good.

    The above assumes you wrap your searchRequest in a function that returns an observable like this:

    func searchRequest(search: String, searchType: String) -> Observable<[[String: String]]> {
        return Observable.create { observer in
            searchRequest(search: search, searchType: searchType, completionHandler: { result in
                observer.onNext(result)
                observer.onCompleted()
            })
            return Disposables.create()
        }
    }
    

    The above is a standard pattern that wraps a function that uses a callback into a function that returns an Observable.