2

I have UITableView to show list of files. Each file name is coded with specific code combination and in order to get real file name I have to call my server side with current file name.

Is it possible to call such operation on cellForRowAt indexPath table view delegate function?

var files: [URL]!

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: "FilesManagerCell", for: indexPath) as? FilesManagerCell {
            let currentFile = files[indexPath.row]

            APIClient.getFileInfo(patientId: currentFile.lastPathComponent, onSuccess: { (response) in
                cell.filenameLabel.text = response.file.fileName
                cell.createdDateLabel.text = response.file.creationDate
                cell.fileSizeLabel.text = response.file.fileSizeWithToken               
                cell.filePath = currentFile
                return cell <- Here I get expected error

           }, onError: { (errorResponse, errorString) in                
        })

        }
        return UITableViewCell()
rmaddy
  • 314,917
  • 42
  • 532
  • 579
AlexBerd
  • 1,368
  • 2
  • 18
  • 39
  • Move your request to the FilesManagerCell class, so it can then process the request asynchronously. You'll also need to override `prepareForReuse` to stop the request if the cell is reused. So the `cellForRowAt` function should only configure the cell, and you can create a function such as `downloadFile(_ filePath: String)` to handle the API request. – JonJ Nov 01 '18 at 13:49
  • Possible duplicate of [Returning data from async call in Swift function](https://stackoverflow.com/questions/25203556/returning-data-from-async-call-in-swift-function) – Scriptable Nov 01 '18 at 15:03
  • Apart from it cannot work anyway don't do that. `cellForRow` is for updating and displaying UI elements. Perform the asynchronous tasks in `viewDidLoad/viewWillAppear` or even in the model. Consider that a cell can be deallocated at any time when the user scrolls. – vadian Nov 01 '18 at 18:24

1 Answers1

2

The cellForRowAt requires you to return a cell synchronously.

Customize your cell by passing the responsibility of each individual cell to load its contents, based on the request - thus reloading its own UI.

To illustrate:

This would be your cell (with your outlets already connected):

class FilesManagerCell: UITableViewCell {

    @IBOutlet private weak var filenameLabel: UILabel!
    @IBOutlet private weak var createdDateLabel: UILabel!
    @IBOutlet private weak var fileSizeLabel: UILabel!

    var filePath: URL! {
        didSet {
            fetchContents()
        }
    }

    override func awakeFromNib() {
        super.awakeFromNib()

        //Here you should create the "Loading behavior", i.e:
        self.filenameLabel.text = "Loading"
        self.createdDateLabel.text = "Loading"
        self.fileSizeLabel.text = "Loading"          
    }

    private func fetchContents() {
        APIClient.getFileInfo(patientId: filepath.lastPathComponent, onSuccess: { response in
            DispatchQueue.main.async {
                self.filenameLabel.text = response.file.fileName
                self.createdDateLabel.text = response.file.creationDate
                self.fileSizeLabel.text = response.file.fileSizeWithToken               
            }
        }, onError: { (errorResponse, errorString) in
            //Customize your error code, i.e:
            DispatchQueue.main.async {
                self.filenameLabel.text = "Error"
                self.createdDateLabel.text = "Error"
                self.fileSizeLabel.text = "Error"
            }
        })
    }
}

And your tableView cellForRowAt implementation:

//...
var files: [URL]!

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if let cell = tableView.dequeueReusableCell(withIdentifier: "FilesManagerCell", for: indexPath) as? FilesManagerCell {     
        let currentFile = files[indexPath.row]
        cell.filePath = currentFile
    }

    return UITableViewCell()
}
//...

EDIT: There is no problem to delegate this responsibility to the cell itself, but be aware that when a cell is being reused, it will trigger a new fetchContents() when setting the filePath property, thus triggering a new API call - but there will be some data "inconsistency", because the while the fetch does not return, it will have the previous fetch contents. One solution to this could be resetting to "loading" state every time you set the filePath, something like this:

    var filePath: URL! {
        didSet {
            // Reset the current fields
            self.filenameLabel.text = "Loading"
            self.createdDateLabel.text = "Loading"
            self.fileSizeLabel.text = "Loading"

            // And then fetch the contents
            fetchContents()
        }
    }
Guilherme Matuella
  • 2,193
  • 1
  • 17
  • 32