Search code examples
iosswiftuitableviewuidocument

How to ensure the order of list shown in UITableView when getting data for cell from UIDocument


My app fetches data via FileManager for each cell in UITableView. The cells need data from a file which requires open UIDocument objects. However, it seems like code inside open completion handler get executed non predictably, so the cells don't get displayed in the order I wish.

How do I solve this problem? I appreciate if anyone gives any clue.

override func viewWillAppear(_ animated: Bool) {
   super.viewWillAppear(true)
   fetchCellsData()
}

func fetchCellsData() {

    do {
        let files = getUrlsOfFiles()    //files in a certain order
        for file in files {

            let document = MyDocument(fileURL: file) //MyDocument subclassing UIDocument

            //open to get data for cell
            document.open { (success) in
                if !success {
                    print("File not open at %@", file)
                    return
                }

                let data = populateCellData(document.fileInfo)

                self.cellsData.append(data)
                self.tableView.reloadData()

                /// tried below also
                //  DispatchQueue.main.async {
                //    self.cellsData.append(data)
                //    self.tableView.reloadData()
                //  }
            }
            document.close(completionHandler: nil)
        }

    } catch {
        print(error)
    }
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! MyCell
    let fileInfo = cellsData[indexPath.row].fileInfo
    //assign fileInfo into MyCell property
    return cell
}

I expect cells get rendered in the order of 'files', however, it seems like the order is a bit unpredictable presuming that it's due to the fact that 'cellForRowAt' gets called before it knows about the full set of 'cellsData'.


Solution

  • From Apple documentation on UIdocument . Apple doc:

    open(completionHandler:)

    Opens a document asynchronously.

    Which means that even if you trigger document.open in the right order, nothing guarantees that the completionHandler sequence will be in the same order, this is why the order is unpredictible.

    However, you know that they will eventually all get done.

    What you could do is :

    1 - place all your datas from opened document into another list

    2 - order this list in accordance to your need

    3 - place this list into cellsData (which I assume is bound to your tableViesDatasource)

    
    var allDatas: [/*your data type*/] = []
    
    ...
    
        do {
            // reset allDatas 
            allDatas = []
            let files = getUrlsOfFiles()
    
            for file in files {
    
                let document = MyDocument(fileURL: file) 
                document.open { (success) in
                    if !success {
                        print("File not open at %@", file)
                        return
                    }
    
                    let data = populateCellData(document.fileInfo)
    
                    self.allDatas.append(data) // "buffer" list
                    self.tableView.reloadData()  
                }
                document.close(completionHandler: nil)
            }
    
        } catch { ... 
    

    Then you change cellsData to a computed property like so :

    var cellsData: [/*your data type*/] {
       get {
          return allDatas.order( /* order in accordance to your needs */ )
       }
    }
    

    this way, each time a new data is added, the list is orderer before being redisplayed.


    Discussion

    this is the easier solution regarding the state of your code, however this may not be the overall prefered solution. For instance in your code, you reload your tableview each time you add a new value, knowing that there will be more data added after, which is not optimised.

    I suggest you to read on Dispatch Group, this is a way to wait until all asynchronous operation your triggered are finished before executing certain actions (such as reloading your tableview in this case) (Readings: Raywenderlich tuts)