Search code examples
iosswiftchartsuicollectionviewcocoapods

collection view presentation of graph: "no chart data available" for Charts cocoapods


I'm coding a GraphViewController class that houses an array of graphs (of type LineChartView). However, when I attempt to display these graphs in the format of cells of a collection view (using the called class GraphCell), the LineChartView objects don't seem to load any data, even though these functions are called inside the GraphViewController class. Here are the relevant bits of my code so far:

class GraphViewController: UIViewController {
    
    //lazy var only calculated when called
    lazy var lineChartView: LineChartView = {
        let chartView = LineChartView()
        chartView.backgroundColor = .systemBlue
        chartView.rightAxis.enabled = false //right axis contributes nothing
        
        let yAxis = chartView.leftAxis
        yAxis.labelFont = .boldSystemFont(ofSize: 12)
        yAxis.setLabelCount(6, force: false)
        yAxis.labelTextColor = .white
        yAxis.axisLineColor = .white
        yAxis.labelPosition = .outsideChart
        
        let xAxis = chartView.xAxis
        xAxis.labelPosition = .bottom
        xAxis.labelFont = .boldSystemFont(ofSize: 12)
        xAxis.setLabelCount(6, force: false)
        xAxis.labelTextColor = .white
        xAxis.axisLineColor = .systemBlue
        
        chartView.animate(xAxisDuration: 1)
        
        return chartView
    }()
    
    var graphColl: UICollectionView!
    var graphs: [LineChartView] = []
    var graphReuseID = "graph"
    
    var homeViewController: ViewController = ViewController()
    
    let dataPts = 50
    var yValues: [ChartDataEntry] = []
    var allWordsNoPins: [Word] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        
        setUpViews();
        setUpConstraints()
        
    }
    
    func setUpViews(){
        let layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = padding/3.2
        layout.scrollDirection = .vertical
        
        graphColl = UICollectionView(frame: .zero, collectionViewLayout: layout)
        graphColl.translatesAutoresizingMaskIntoConstraints = false
        graphColl.dataSource = self
        graphColl.delegate = self
        
        //must register GraphCell class before calling dequeueReusableCell
        graphColl.register(GraphCell.self, forCellWithReuseIdentifier: graphReuseID)
        
        graphColl.backgroundColor = .white

        view.addSubview(graphColl)
        
        print("stuff assigned")
        assignData()
        setData()
        
        graphs.append(lineChartView)
        
        
    }

One can assume setUpConstraints() is working correctly, as the graph collection does show up. Here are all the functions that have to deal with the collection view I'm using:

//INSIDE GraphViewController
extension GraphViewController: UICollectionViewDelegateFlowLayout{
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize{
        
        //collectionView.frame.height -
        
        let size = 25*padding
        let sizeWidth = collectionView.frame.width - padding/3
        return CGSize(width: sizeWidth, height: size)
    }
    
}

extension GraphViewController: UICollectionViewDataSource{
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        
        return graphs.count //total number of entries
    }
    
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let graphColl = collectionView.dequeueReusableCell(withReuseIdentifier: graphReuseID, for: indexPath) as! GraphCell
        
        graphColl.configure(graph: graphs[indexPath.item])
        return graphColl
    }
}

and:

//configure function INSIDE GraphCell
    func configure(graph: LineChartView){
        chartView = graph
    }

Here are the assignData() and setData() functions:

func setData(){
        let set1 = LineChartDataSet(entries: yValues, label: "Word Frequency")
        let data = LineChartData(dataSet: set1)
        
        set1.drawCirclesEnabled = true
        set1.circleRadius = 3
        set1.mode = .cubicBezier //smoothes out curve
        set1.setColor(.white)
        set1.lineWidth = 3
        set1.drawHorizontalHighlightIndicatorEnabled = false //ugly yellow line
        
        data.setDrawValues(false)
        
        
        lineChartView.data = data
    }
    
    func assignData(){
        setUpTempWords()
        
        let dataValues = allWordsNoPins
        print(allWordsNoPins.count)
        
        for i in 0...dataPts-1{
            yValues.append(ChartDataEntry(x: Double(i), y: Double(dataValues[i].count)))
        }
        
    }

One can also assume the setUpTempWords() function is working correctly, because of this screenshot below:

Here, I have plotted the lineChartView object of type LineChartView directly on top of the GraphColl UICollectionView variable inside my GraphViewController class. The data is displayed. However, when I try to plot the same graph in my GraphCell class, I get

"No chart data available." I have traced the calls in my GraphViewController class, and based on the way the viewDidLoad() function is set up, I can conclude that the lineChartView setup methods (assignData(), etc) are being called. For reference, here is my GraphCell class code:

import UIKit
import Charts

class GraphCell: UICollectionViewCell {
    
    var chartView = LineChartView()
    var graphCellBox: UIView!
    
    override init(frame: CGRect){
        super.init(frame: frame)
        
        contentView.backgroundColor = .blue
        
        chartView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(chartView)
        
        graphCellBox = UIView()
        graphCellBox.translatesAutoresizingMaskIntoConstraints = false
        //graphCellBox.backgroundColor = cellorange
        graphCellBox.layer.cornerRadius = 15.0
        contentView.addSubview(graphCellBox)
        
        setUpConstraints()
    }
    
    func setUpConstraints(){
        NSLayoutConstraint.activate([
            graphCellBox.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
            graphCellBox.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
            graphCellBox.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            graphCellBox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            
            chartView.topAnchor.constraint(equalTo: graphCellBox.topAnchor),
            chartView.bottomAnchor.constraint(equalTo: graphCellBox.bottomAnchor),
            chartView.leadingAnchor.constraint(equalTo: graphCellBox.leadingAnchor),
            chartView.trailingAnchor.constraint(equalTo: graphCellBox.trailingAnchor),
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    func configure(graph: LineChartView){
        chartView = graph
    }
}

As a side note, changing the lineChartView to a non-lazy (normal) variable does not fix this problem. I suspected the lazy declaration was the problem, since the graph would only be initialize if called, but this was not the case. Thank you for reading this post, and I'd greatly appreciate any direction or guidance!


Solution

  • Tough to test this without a reproducible example, but...

    Assigning chartView = graph looks problematic.

    Try using your graphCellBoxgraphCellBox as a "container" for the LineChartView you're passing in with configure(...):

    class GraphCell: UICollectionViewCell {
        
        var graphCellBox: UIView!
        
        override init(frame: CGRect){
            super.init(frame: frame)
            
            contentView.backgroundColor = .blue
            
            graphCellBox = UIView()
            graphCellBox.translatesAutoresizingMaskIntoConstraints = false
            //graphCellBox.backgroundColor = cellorange
            graphCellBox.layer.cornerRadius = 15.0
            contentView.addSubview(graphCellBox)
            
            setUpConstraints()
        }
        
        func setUpConstraints(){
            NSLayoutConstraint.activate([
                graphCellBox.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
                graphCellBox.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
                graphCellBox.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
                graphCellBox.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            ])
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        
        func configure(graph: LineChartView){
            graph.translatesAutoresizingMaskIntoConstraints = false
            graphCellBox.addSubview(graph)
    
            NSLayoutConstraint.activate([
                graph.topAnchor.constraint(equalTo: graphCellBox.topAnchor),
                graph.bottomAnchor.constraint(equalTo: graphCellBox.bottomAnchor),
                graph.leadingAnchor.constraint(equalTo: graphCellBox.leadingAnchor),
                graph.trailingAnchor.constraint(equalTo: graphCellBox.trailingAnchor),
            ])
        }
    }