I have a dataViewController, which needs data from an API in order to populate a graph, and some news. The news API call is very fast and easy, but the graph call completely slows down the page scrolling as it is called in viewDidLoad(). I edited the graph API to download the data and then store it to cache, and instead on viewDidLoad, it checks if there is any data in cache, and then if there is some it uses that, but it is still terrible slow when scrolling pages. How can I fix this? Here is some of my DataViewController code that handles everything to do with the chart:
extension DataViewController {
func GetOnlyDateMonthYearFromFullDate(currentDateFormate:NSString , conVertFormate:NSString , convertDate:NSString ) -> NSString
{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = currentDateFormate as String
let formatter = DateFormatter()
formatter.dateFormat = "yyyy'-'MM'-'dd'-'HH':'mm':'ssZZZ" as String
let finalDate = formatter.date(from: convertDate as String)
formatter.dateFormat = conVertFormate as String
let dateString = formatter.string(from: finalDate!)
return dateString as NSString
}
//Charts
@objc func handleLongPress(longPressGesture:UILongPressGestureRecognizer) {
let p = longPressGesture.location(in: self.chartView)
if longPressGesture.state == UIGestureRecognizer.State.began && isLineChartExpanded == false {
mainLabelContainer.fadeOut()
chartViewExpandedConstraints()
isLineChartExpanded = true
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
} else if longPressGesture.state == UIGestureRecognizer.State.began && isLineChartExpanded == true {
mainLabelContainer.fadeIn()
chartViewLandscapeConstraints()
isLineChartExpanded = false
UIView.animate(withDuration: 0.5) {
self.view.layoutIfNeeded()
}
}
}
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
var xInt = Int()
//The Currency Unit taken from the exchange section of the API.
xInt = Int(entry.x)
let currencyUnit = CGExchange.shared.exchangeData[0].rates[defaultCurrency]!.unit
chartPriceLabel.textColor = UIColor.white.withAlphaComponent(0.5)
chartDateLabel.textColor = UIColor.white.withAlphaComponent(0.5)
chartPriceLabel.isHidden = false
chartDateLabel.isHidden = false
chartPriceLabel.text = "\(currencyUnit)\(round(1000*entry.y)/1000)"
let date = self.GetOnlyDateMonthYearFromFullDate(currentDateFormate: "yyyy-MM-dd'T'HH:mm:ss.SSSZ", conVertFormate: "MMM d, h:mm a", convertDate: self.days[xInt] as NSString)
chartDateLabel.text = "\(date as String)"
}
//Graph Buttons and states
enum GraphStat {
case day, fortnight, month
}
@objc func todayButtonAction(sender: UIButton!) {
self.prices = []
self.days = []
CGCharts.shared.graphSetup = .day
CGCharts.shared.getData(coin: self.dataObject, defaultCurrency: self.defaultCurrency, arr: true, completion: { (success) in
self.prepareGraph(arr: true, completion: { (success) in
DispatchQueue.main.async {
self.chartView.animate(xAxisDuration: 4.0)
}
})
})
}
@objc func fortnightButtonAction(sender: UIButton!) {
self.prices = []
self.days = []
CGCharts.shared.graphSetup = .week
CGCharts.shared.getData(coin: self.dataObject, defaultCurrency: self.defaultCurrency, arr: true, completion: { (success) in
self.prepareGraph(arr: true, completion: { (success) in
DispatchQueue.main.async {
self.chartView.animate(xAxisDuration: 4.0)
}
})
})
}
@objc func monthButtonAction(sender: UIButton!) {
self.prices = []
self.days = []
CGCharts.shared.graphSetup = .month
CGCharts.shared.getData(coin: self.dataObject, defaultCurrency: self.defaultCurrency, arr: true, completion: { (success) in
self.prepareGraph(arr: true, completion: { (success) in
DispatchQueue.main.async {
self.chartView.animate(xAxisDuration: 4.0)
}
})
})
}
func lineChartUpdate(dataPoints: [String], values: [Double]) {
if CGCharts.shared.graphSetup == .day {
graphSetup = .day
} else if CGCharts.shared.graphSetup == .week {
graphSetup = .fortnight
} else if CGCharts.shared.graphSetup == .month {
graphSetup = .month
}
//Graph State buttons switch status for highlighting buttons.
switch graphSetup {
case .day:
todayButton.alpha = 0.5
fortnightButton.alpha = 1.0
monthButton.alpha = 1.0
case .fortnight:
fortnightButton.alpha = 0.5
todayButton.alpha = 1.0
monthButton.alpha = 1.0
case .month:
monthButton.alpha = 0.5
todayButton.alpha = 1.0
fortnightButton.alpha = 1.0
}
//Graph data management
var lineChartEntry = [ChartDataEntry]()
for i in 0..<prices.count {
//Graph marker from extension
if prices != [] {
let value = ChartDataEntry(x: Double(i), y: values[i])
lineChartEntry.append(value)
let line1 = LineChartDataSet(values: lineChartEntry, label: "Price")
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale(identifier: "en_US")
let dateObjects = self.days.compactMap { dateFormatter.date(from: $0) }
let dateStrings = dateObjects.compactMap { dateFormatter.string(from: $0) }
self.chartView.xAxis.valueFormatter = DefaultAxisValueFormatter(block: {(index, _) in
return dateStrings[Int(index)]
})
line1.setColor(.white)
line1.drawVerticalHighlightIndicatorEnabled = true
line1.drawHorizontalHighlightIndicatorEnabled = true
line1.mode = .cubicBezier
line1.lineWidth = 2.0
line1.drawValuesEnabled = true
line1.valueTextColor = UIColor.white
line1.drawCirclesEnabled = false
chartView.xAxis.valueFormatter = IndexAxisValueFormatter(values:dateStrings)
chartView.xAxis.granularity = 1
chartView.leftAxis.drawGridLinesEnabled = false
chartView.xAxis.drawGridLinesEnabled = false
//Expanded
chartView.rightAxis.enabled = false
chartView.leftAxis.enabled = false
chartView.xAxis.enabled = false
chartView.rightAxis.drawGridLinesEnabled = false
chartView.legend.enabled = false
chartView.dragEnabled = false
chartView.pinchZoomEnabled = false
chartView.drawMarkers = false
chartView.doubleTapToZoomEnabled = false
chartView.isUserInteractionEnabled = true
//Graph Data.
let data = LineChartData()
data.addDataSet(line1)
chartView.data = data
}
}
}
//Dismiss Keyboard when Tap
override func touchesBegan(_ touches: Set<UITouch>,
with event: UIEvent?) {
self.view.endEditing(true)
}
//GraphData
func prepareGraph(arr: Bool, completion: @escaping (Bool) -> ()) {
if Storage.fileExists("\(dataObject)GraphData", in: Storage.Directory.caches) {
print("Exists")
self.priceData = Storage.retrieve("\(dataObject)GraphData", from: Storage.Directory.caches, as: [Price].self)
self.days = self.priceData.map({ $0.date.description })
self.prices = self.priceData.map({ $0.price })
DispatchQueue.main.async {
// self.chartView.animate(xAxisDuration: 4.0)
self.lineChartUpdate(dataPoints: self.days, values: self.prices)
}
} else {
self.prices = []
self.days = []
print("didn'tExist")
CGCharts.shared.graphSetup = .day
print("ChartsCleared")
CGCharts.shared.getData(coin: self.dataObject, defaultCurrency: self.defaultCurrency, arr: true) { (success) in
self.updateGraph()
DispatchQueue.main.async {
self.lineChartUpdate(dataPoints: self.days, values: self.prices)
}
}
}
}
//GraphData In Storage
func updateGraph() {
self.priceData = Storage.retrieve("\(dataObject)GraphData", from: Storage.Directory.caches, as: [Price].self)
self.days = self.priceData.map({ $0.date.description })
self.prices = self.priceData.map({ $0.price })
DispatchQueue.main.async {
// self.chartView.animate(xAxisDuration: 4.0)
self.lineChartUpdate(dataPoints: self.days, values: self.prices)
}
}
}
Here is the actual API call from the API manager file:
import Foundation
struct Root: Codable {
let prices: [Price]
}
struct Price: Codable {
let date: Date
let price: Double
}
class CGCharts {
var priceData = [Price]()
static let shared = CGCharts()
var currency = ""
var days = ""
enum GraphStatus {
case day, week, month
}
var graphSetup = GraphStatus.day
func getAllCharts(arr: Bool, completion: @escaping (Bool) -> ()) {
}
func getData(coin: String, defaultCurrency: String, arr: Bool, completion: @escaping (Bool) -> ()) {
switch graphSetup {
case .day:
days = "1"
case .week:
days = "14"
case .month:
days = "30"
}
let urlJSON = "https://api.coingecko.com/api/v3/coins/\(coin)/market_chart?vs_currency=\(defaultCurrency)&days=\(days)"
guard let url = URL(string: urlJSON) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let prices = try JSONDecoder().decode(Root.self, from: data).prices
print(prices.first!.date.description(with:.current)) // "Saturday, September 1, 2018 at 6:25:38 PM Brasilia Standard Time\n"
print(prices[0].price)
self.priceData = prices
Storage.store(self.priceData, to: Storage.Directory.caches, as: "\(coin)GraphData")
completion(arr)
} catch {
print(error)
}
}.resume()
}
}
extension Price {
public init(from decoder: Decoder) throws {
var unkeyedContainer = try decoder.unkeyedContainer()
let date = try unkeyedContainer.decode(UInt64.self).date
let price = try unkeyedContainer.decode(Double.self)
self.init(date: date, price: price)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(date.unixEpochTime)
try container.encode(price)
}
}
extension UInt64 {
var date: Date {
return Date(timeIntervalSince1970: TimeInterval(self)/1000)
}
}
extension Date {
var unixEpochTime: UInt64 {
return UInt64(timeIntervalSince1970*1000)
}
}
Something in one of these two files is just too slow, making page scrolling really annoying. It's only about 2 seconds, but it freezes the paging animation for that 2 seconds, I need to find a way to make scrolling smooth and responsive, even if it means not showing any chart data for a couple of seconds until it loads. How can I do this, and what is it exactly that is slowing everything down so much? I also have a suspition it could be to do with:
self.priceData = Storage.retrieve("\(dataObject)GraphData", from: Storage.Directory.caches, as: [Price].self)
self.days = self.priceData.map({ $0.date.description })
self.prices = self.priceData.map({ $0.price })
as this is looping through a big struct with the data and then adding them to 2 arrays, maybe that is holding everything up while it occurs? Thank you.
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
guard let self = self else {
return
}
self.priceData = Storage.retrieve("\(dataObject)GraphData", from:Storage.Directory.caches, as: [Price].self)
self.days = self.priceData.map({ $0.date.description })
self.prices = self.priceData.map({ $0.price })
DispatchQueue.main.async { [weak self] in
// update your UI here based on the data
}
}