I've just implemented the library Charts (https://github.com/danielgindi/Charts) in a tableview, but I experience pretty heavy lag during scrolling, when my charts hold a lot of data.
I have a method inside the ChartTableViewCell where I draw the chart based on the data I pass and call from my viewcontroller.
func updateCell(chartData: ChartData) { DispatchQueue.global(qos: .background).async { print("This is run on the background queue") self.readings = (readings != nil) ? readings!.readings : [] self.period = period if (chartData.isKind(of: LineChartData.self)) { data.lineData = chartData as! LineChartData } else if (chartData.isKind(of: BarChartData.self)) { data.barData = chartData as! BarChartData } } DispatchQueue.main.async { self.chartView.data = data } }
In my tableViewController I call the function after parsing the data:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let meta = chartMetas[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "chartCell", for: indexPath) as! ChartTableViewCell cell.readings = currentReadings cell.updateCell() return cell }
What did I miss? Since the view is lagging so hard when scrolling.
UPDATE:
I tried, as suggested, to prepare the chart data in the viewcontroller and pass it to the cell. However it seems like the problem in the resuseable cells, the lags appears when I scroll and a new cell enters the screen. How can I fix this? It is pretty annoying.
UPDATE 2: It looks like the Charts aren't supposed to be used in a tableview... https://github.com/danielgindi/Charts/issues/3395
3 Answers
Answers 1
To get a serious performance boost, you'll likely need to implement UITableViewDataSourcePrefecting
. By doing this, the table view will call into your delegate to let you know that a cell will be needed ahead of time. This will give your code a chance to prepare and render any data it needs.
From the documentation:
You use a prefetch data source object in conjunction with your table view’s data source to begin loading data for cells before the tableView(_:cellForRowAt:) data source method is called.The following steps are required to add a prefetch data source to your table view:
Create the table view and its data source.
Create an object that adopts the UITableViewDataSourcePrefetching protocol, and assign it to the prefetchDataSource property on the table view.
Initiate asynchronous loading of the data required for the cells at the specified index paths in your implementation of tableView(_:prefetchRowsAt:).
Prepare the cell for display using the prefetched data in your tableView(_:cellForRowAt:) data source method.
Cancel pending data load operations when the table view informs you that the data is no longer required in the tableView(_:cancelPrefetchingForRowsAt:) method.
Answers 2
Try to prepare all the chart data before you pass it to the cell. I mean make all the stuff you do I the updateCell() func in your tableViewController probably in viewDidLoad and pass the generated chart Datas to an array.
Then in tableView(tableView: , cellForRowAt indexPath: ) just pass the previously generated data to your cell.
So your updateCell func should looks like so:
func updateCell(lineChartData: LineChartData) { // SET DATA AND RELOAD chartView.data = lineChartData chartView.fitScreen() }
And you tableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let meta = chartMetas[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "chartCell", for: indexPath) as! ChartTableViewCell cell.readings = currentReadings cell.updateCell(lineChartData: arrayOfGeneratedChartData[indexPath.row]) return cell }
Seems that you keeps entries in the cell, so don't do this. Keep them in the tableViewController. Pass only required data to the cell and don't do hard tasks in the cell methods.
Answers 3
You chart library is probably doing some pretty heavy processing when there are large data sets. If the author of this library didn't account for this, you are executing this heavy processing on the main thread every time you scroll and a new cell appears on the screen. This will certainly cause stuttering.
I would look to change the cell setup method to simply display a spinner or placeholder table, and then kick off the loading of data and building of the chart on a background thread. This should cause the UITableView
scrolling to be smooth, but you will see the cells with placeholders as you scroll through the table, where the charts get populated after the background thread processing completes.
Running things on a background thread in Swift is pretty easy.
Just make sure when you are ready to update the UI with the chart, you do that on the main thread (Al UI updates should be executed on the main thread or you will see weird visual oddities or delays in the UI being updated). So maybe hide the placeholder image and show the chart using animations on the main thread after the heavy processing is done on the background thread.
What if you did something like this?
func updateCell(chartData: ChartData) { DispatchQueue.global(qos: .background).async { print("This is run on the background queue") self.readings = (readings != nil) ? readings!.readings : [] self.period = period if (chartData.isKind(of: LineChartData.self)) { data.lineData = chartData as! LineChartData } else if (chartData.isKind(of: BarChartData.self)) { data.barData = chartData as! BarChartData } self.chartView.data = data DispatchQueue.main.async { // something that triggers redrawing of the chartView. chartView.fitScreen() } } }
Note that the main thread call is inside the background thread execution block. This means all the stuff will run on the background thread, and when that background thread stuff is done, it will call something in the chart library that will do the UI refresh. Again, this won't help if your chart library is written to force long running operations to happen on the main thread.
0 comments:
Post a Comment