0

My certain two rows of data of UITableViewCell exchange, and this is how it looks. The row data after exchanged are the right, and they're wrong before exchanged.

And I'm sorry for not have enough reputation to post .gif image.

Is there any way to avoid the data exchange to the wrong row?

The links of problems above seem to be related to mine, but I think my problem is not caused by scrolling.

I setup UITableViewCell with DispatchQueue.global(qos: .background).async, and this function is inside the cell itself.

--SensorRecordTableViewCell (UITableViewCell)--

func getNewLiveInfo(shedId: String, sensorCategory: String) {

        DispatchQueue.global(qos: .background).async {

            let id = self.shedId.cString(using: .utf8)
            let shed_id = UnsafeMutablePointer(mutating: id)

            let category = self.sensorCategory.cString(using: .utf8)
            let sensor_category = UnsafeMutablePointer(mutating: category)

            if let data = getliveInfo(shed_id, sensor_category) {
                let formatter = DateFormatter()
                formatter.locale = Locale(identifier: "en_US_POSIX")
                formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

                // record_id, sensor_id, sensor_category, sensor_value, read_time
                self.sensorRecord.recordId = String(cString: data.pointee!)
                self.sensorRecord.sensorId = String(cString: (data+1).pointee!)
                self.sensorRecord.sensorCategory = String(cString: (data+2).pointee!)
                self.sensorRecord.value = Double(String(cString: (data+3).pointee!))
                self.sensorRecord.time = formatter.date(from: String(cString: (data+4).pointee!))

                DispatchQueue.main.async {
                    self.setValueAndTime()

                }
                data.deallocate()
            }
        }     
    }

And call the function above from func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)

--UITableViewDelegate, UITableViewDataSource--

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {

        var cell = cell as! SensorRecordTableViewCell
        cell.getNewLiveInfo(shedId: shed.id!, sensorCategory: config.sensorRecordOrder[indexPath.row])
}

Finally, I setup the cell from func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: sensorCellId, for: indexPath) as! SensorRecordTableViewCell
        cell.setUpView(shedId: shed.id!, sensorCategory: config.sensorRecordOrder[indexPath.row])
            return cell
}

Is there any way to avoid the data exchange to the wrong row?

陳香君
  • 39
  • 2
  • 10
  • There are quite a few things going on here that aren't particularly great. Don't perform asynchronous operations in `willDisplayCell` - Don't use `.background` QoS, use `.utility` or `.userInitiated` - If `getliveInfo` is a network operation, why isn't it synchronous? Don't use `UnsafeMutablePointer` without a *really good reason* - Can't you have your data returned in an appropriate struct? You should look at [`prefetchDataSource`](https://developer.apple.com/documentation/uikit/uitableview/1771763-prefetchdatasource) to fetch data on demand – Paulw11 Sep 05 '19 at 00:15
  • @Paulw11 I need to communicate with PostgreSQL database, and I get the data from ```getLiveInfo```, which is a C function, and it is why I use ```UnsafeMutablePointer```. If you have other opinion of the way to connect and get data from PostgreSQL, please tell me. And thank you for your answer, I'll find out the way to use ```prefetchDataSource```. – 陳香君 Sep 05 '19 at 00:47
  • Is it a local database? ie. on the device? – Paulw11 Sep 05 '19 at 01:15
  • @Paulw11 No, connect to db of server – 陳香君 Sep 05 '19 at 01:18
  • You would typically put a web service in front of a remote database so that you can interact with it using a RESTful API and JSON. You would also typically fetch data independent of what is being displayed on the table view - Otherwise you incur network operations as the table scrolls and you have delays in showing the cells as you are seeing here. – Paulw11 Sep 05 '19 at 01:20
  • @Paulw11 Yeah..But I can't interact with a web service. It's another part of the project, and I don't have right to change it. All I can do is to access the DB directly from the client side. – 陳香君 Sep 05 '19 at 03:08
  • Ok, Regardless of the architecture, you should avoid fencing data in the table view display records - fetch the data before you need it, either all at once if there isn't too much or use the prefetch data source methods – Paulw11 Sep 05 '19 at 03:10
  • @Paulw11 Is it the delays of data showing? It just exchange 2 rows of data. – 陳香君 Sep 05 '19 at 03:11
  • Because you have asynchronous operations occurring object references are getting mixed up. Also cells are re-used as you scroll, so you may have a pending operation complete for a cell that has since been re-used. – Paulw11 Sep 05 '19 at 03:12
  • @Paulw11 So I need to get the data first, then fetch them to my tableview, right? Can't I get the data and fetch them asynchronously when need to display them? – 陳香君 Sep 05 '19 at 03:16
  • Ok.. I think I got it. I just want to avoid my ViewController to be fat, so I put the fetch code to UITableViewCell itself. – 陳香君 Sep 05 '19 at 03:18
  • It is certainly far easier to fetch the data first. It also tends to make your app more responsive; you incur a slight delay to load the data and then when the user scrolls the data is already available. Loading on demand has much more complicated logic as you need to account for data you have already fetched as you scroll as well as cell reuse and it introduces delays in showing the data to the user. – Paulw11 Sep 05 '19 at 03:18
  • Cells should not be involved in fetching data; they are a lightweight view. You should have a data model class that fetches the data. The view controller simply connects the model to the cell (view) - Model - View - Controller – Paulw11 Sep 05 '19 at 03:19
  • @Paulw11 Thank you for explanation. Maybe I should learn the proper way to load on demand. – 陳香君 Sep 05 '19 at 03:23
  • @Paulw11 I know MVC and MVVM, but there is still some difficulty when implementing them. It always turns out that my model class is quite useless. – 陳香君 Sep 05 '19 at 03:26

1 Answers1

0

Here is a simple example to show what it should looks like, hope it helps.

class SensorRecord {
    var recordID: Int = 0
    var sensorID: Int = 0
}

class ViewController: UIViewController {

    private var dataSource = [SensorRecord]()

    override func viewDidLoad() {
        super.viewDidLoad()

        // request infos from your server
        getNewLiveInfo(completion: { (sensorRecords)
            // after the request success, reload data
            self.dataSource = sensorRecords
            self.tableView.reloadData()
        })
    }

}

// MARK: UITableViewDataSource
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataSource.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ID", for: indexPath)

        // let your cell to show datas from your server
        let sensorRecord = dataSource[indexPath.row]
        cell.update(with: sensorRecord)

        return cell
    }
}
Liam
  • 172
  • 1
  • 10