2

I'm using Daniel Gindi's Charts library (iOS-charts), and in order to keep things simple I'm using a custom UIView to store the chart view (as a ChartViewBase). I have a class hierarchy of View Controller classes where the leaf class has the reference to the UIView with the ChartViewBase in it. The problem is that the chart is refusing to display, all I get is a black rectangle where the chart is supposed to be. I guess the code would probably be the most useful, so here goes nothing:
The generic UIView chart-holding class:

import UIKit
import Charts

protocol GenericChartViewDelegate: class {
func backButtonTapped()
func switchChartButtonTapped()
func finalizeResultsButtonTapped()
}

class GenericChartView: UIView, ChartViewDelegate
{

@IBOutlet weak var headerDisplay: UIButton!
@IBOutlet var view: UIView!
weak var delegate: GenericChartViewDelegate?
required init?(coder aDecoder: NSCoder)
{
    super.init(coder: aDecoder)
    Bundle.main.loadNibNamed("GenericChartView", owner: self, options: nil)
    self.addSubview(self.view)
    chart.delegate = self
}
override init(frame: CGRect)
{
    super.init(frame: frame)
    Bundle.main.loadNibNamed("GenericChartView", owner: self, options: nil)
    self.addSubview(self.view)
    chart.delegate = self
}
@IBOutlet var chart: ChartViewBase!
@IBAction func BackButton(_ sender: UIButton) {
    delegate?.backButtonTapped()
}

@IBAction func SwitchChartButton(_ sender: UIButton) {
    delegate?.switchChartButtonTapped()
}

@IBAction func FinalizeResultsButton(_ sender: UIButton) {
    delegate?.finalizeResultsButtonTapped()
}
func setHeader(header: String)
{
    headerDisplay.setTitle(header, for: UIControlState.normal)
    headerDisplay.layer.cornerRadius = MyVariables.borderCurvature
    headerDisplay.titleLabel?.adjustsFontSizeToFitWidth = true

}
}

Now the base chart view controller:

class ChartViewControllerBase: UIViewController
{

var myColors : [NSUIColor] = []
//var bounds : CGRect!
override func viewDidLoad() {
    super.viewDidLoad()
    let sessionCount = ShareData.sharedInstance.currentSessionNumber
    //NotificationCenter.default.addObserver(self, selector: "rotated", name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
    createChart()
    var myBase = getChartObject()

    var myChartView = getChartDisplayView()
    setDelegate()
    if let myName = ShareData.sharedInstance.currentAccount.name {//sessionName.characters.last!
        let myTitle = "Session " + "\(sessionCount)" + " Results - " + myName

        myChartView.setHeader(header: myTitle)
    }

    //chartOriginalBounds = chart.bounds
    if let resultChoice = ShareData.sharedInstance.sessionDataObjectContainer[ShareData.sharedInstance.currentAccountSessionNames[sessionCount - 1]]?.chartInfo
    {
        makeChart(resultChoice: resultChoice)
    }

    // Do any additional setup after loading the view.
}

func setDelegate()
{

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}
func createChart()
{

}
func getChartDisplayView() -> GenericChartView
{
    return GenericChartView(frame: self.view.bounds)
}

func getChartObject() -> ChartViewBase
{
    return ChartViewBase()
}

func makeChart(resultChoice: chartData)
{

}

func getColors(value: Double)
{

}

func getChartDataEntry(xval: Double, yval: Double) -> ChartDataEntry
{
    return ChartDataEntry(x: xval, y: yval)
}
}

Now the LineAndBarChart code:

class LineAndBarChartViewControllerBase: ChartViewControllerBase {

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

override func getChartObject() -> ChartViewBase
{
    return ChartViewBase()
}
var yAxis: YAxis!

func setYAxisFormatter() {

}

func createSet(myEntries: [ChartDataEntry])
{

}



override func makeChart(resultChoice: chartData)
{
    let chart = getChartObject() as! BarLineChartViewBase
    chart.chartDescription?.enabled = false
    chart.drawGridBackgroundEnabled = false
    chart.dragEnabled = true
    chart.setScaleEnabled(true)
    chart.pinchZoomEnabled = true
    let xAxis = chart.xAxis
    xAxis.labelPosition = XAxis.LabelPosition.bottom
    chart.rightAxis.enabled = false
    xAxis.drawGridLinesEnabled = false
    xAxis.drawAxisLineEnabled = true
    xAxis.granularity = 1.0


    //xAxis.setValuesForKeys(<#T##keyedValues: [String : Any]##[String : Any]#>)
    yAxis = chart.leftAxis
    chart.rightAxis.enabled = false
    chart.legend.enabled = true
    yAxis.granularity = 1.0
    yAxis.drawGridLinesEnabled = false
    var counter = 1.0
    var myEntries : [ChartDataEntry] = []

    var myXValues : [String] = []


    if !resultChoice.plotPoints.isEmpty
    {
        for var item in resultChoice.plotPoints
        {
            let timeString : String =
                {
                    let formatter = DateFormatter()
                    formatter.dateFormat = "h:mm a"
                    formatter.amSymbol = "AM"
                    formatter.pmSymbol = "PM"
                    return formatter.string(from: item.xValue)
            }()
            myXValues.append(timeString)
            /*if(item.showXValue)
             {
             myXValues.append(timeString)
             }
             else
             {
             myXValues.append("")
             }*/
            let myEntry = getChartDataEntry(xval: counter, yval: Double(item.yValue))
            getColors(value: myEntry.y)

            counter += 1
            myEntries.append(myEntry)

        }
        let myFormatter = MyXAxisValueFormatter(values: myXValues)

        xAxis.valueFormatter = myFormatter
        setYAxisFormatter()
        createSet(myEntries: myEntries)

    }

}
}

class MyXAxisValueFormatter: NSObject, IAxisValueFormatter {

var mValues: [String]

init(values: [String]) {
    self.mValues = values;
}


func stringForValue( _ value: Double, axis: AxisBase?) -> String
{
    // "value" represents the position of the label on the axis (x or y)
    let myVal: Int = Int(value)
    if (myVal < mValues.count)
    {
        return mValues[myVal];
    }
    else
    {
        return "ERROR"
    }
}
}

Then the bar chart base code:

class BarChartViewControllerBase: LineAndBarChartViewControllerBase {

override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}



override func createChart()
{
    var myChart = getChartDisplayView()
    myChart.chart = BarChartView(frame: myChart.chart.contentRect)
}

override func createSet(myEntries: [ChartDataEntry])
{
    let chart = getChartObject() as! BarChartView
    let mySet = BarChartDataSet(values: myEntries, label: "Values")
    mySet.colors = myColors
    mySet.valueColors = myColors
    setValueFormatter(set: mySet)

    let myBarChartData = BarChartData(dataSet: mySet)
    myBarChartData.barWidth = 0.8
    chart.data = myBarChartData
}

func setValueFormatter(set: BarChartDataSet)
{

}


override func getChartDataEntry(xval: Double, yval: Double) -> ChartDataEntry
{
    return BarChartDataEntry(x: xval, y: yval)
}
}

And finally, the leaf (most derived class) code:

class DerivedClassChartViewController: BarChartViewControllerBase, GenericChartViewDelegate
{


@IBOutlet weak var chartView: GenericChartView!
override func viewDidLoad() {
    super.viewDidLoad()

    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


override func getChartDisplayView() -> GenericChartView
{
    return chartView
}

override func getChartObject() -> ChartViewBase
{
    return chartView.chart
}

override func getColors(value: Double)
{
    if (value == 0.0)
    {
        myColors.append(UIColor.black)
    }
    else if (value == 1.0)
    {
        myColors.append(MyVariables.colorOne)
    }
    else if (value == 2.0)
    {
        myColors.append(MyVariables.colorTwo)
    }
    else if (value == 3.0)
    {
        myColors.append(MyVariables.colorThree)
    }
    else if (value == 4.0)
    {
        myColors.append(MyVariables.colorFour)
    }
    else if (value == 5.0)
    {
        myColors.append(MyVariables.colorFive)
    }
}

func backButtonTapped()
{
    self.navigationController?.popViewController(animated: false)
}
func switchChartButtonTapped()
{
    let alertController = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
    let goToLineChartAction = UIAlertAction(title: "Line Chart", style: UIAlertActionStyle.default)
    { (alertAction: UIAlertAction!) -> Void in
        self.navigationController?.popViewController(animated: false)
       let viewControllerStoryboardId = "LineChartDisplayViewController"
        let storyboardName = "Main"
        let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
        let viewController = storyboard.instantiateViewController(withIdentifier: viewControllerStoryboardId)
        self.navigationController?.pushViewController(viewController, animated: false)
    }
    alertController.addAction(goToLineChartAction)
    alertController.view.tintColor = UIColor.black
    present(alertController, animated: true, completion:  nil)
    let presentationController = alertController.popoverPresentationController
    presentationController?.sourceView = self.view
    presentationController?.sourceRect = CGRect(x: 330, y: 210, width: 330, height: 210)
}
func finalizeResultsButtonTapped()
{

}

override func setDelegate()
{
    chartView.delegate = self
}
override func setValueFormatter(set: BarChartDataSet)
{
    set.valueFormatter = DerivedClassNumberFormatter()
}
}

class DerivedClassNumberFormatter: NSObject, IValueFormatter {
func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
    var returnVal = "\(Int(value))"

    return returnVal
}
}

I do have a few questions. Do you make the UIView the ChartViewDelegate, or should that be the view controller that holds the UIView that holds the chart object? Am I missing anything obvious? I've used custom UIViews before, so I know that that code is correct (aside from whether the class should be ChartViewDelegate or not, that is- that I don't know). Yes I do need all of these classes, as I will have many, many chart types in the future. Anyhow, any suggestions on what I can do to get the chart to display would be great.

Thanks,
Sean

DevB2F
  • 4,674
  • 4
  • 36
  • 60
Sean Zlatnik
  • 187
  • 2
  • 12
  • Hi Sean. I have [same problem](https://github.com/danielgindi/Charts/issues/2878). Did you solve it? Could you help me, please? – Amir Shabani Oct 15 '17 at 08:13
  • I do not know whether this solution will work for you without knowing how close your code is to mine, but what I did was I made GenericChartView an abstract class and set the variable to BarChartView or LineChartView in the derived classes instead of doing it inside the viewcontroller. Does this make sense? It means adding two or three new classes (depending on how many chart types your code represents) that are just for constructing BarChartView, LineChartView, or PieChartView as necessary. Hopefully this helps you out as it did solve my problem if I remember everything correctly. – Sean Zlatnik Oct 17 '17 at 07:59

1 Answers1

0

I also am using the Charts library. I am not sure if I quite understand your limitation, but I was able to include Charts inside of a custom UIView. I have other methods (not shown) to pass actual data to the Labels and Charts from the View Controller.

Here's how I did it:

import UIKit
import Charts

class MyHeader: UIView, ChartViewDelegate {

// My Header is the top view of the ViewController
// It gives summary information for the user

// MARK: - Properties
var firstLabel = LabelView()
var secondLabel = LabelView()
var firstPie = PieChartView()
var secondPie = PieChartView()
var thirdPie = PieChartView()
let nformatter = NumberFormatter()

// MARK: - LifeCycle
override func awakeFromNib() {
    super.awakeFromNib()
}

override init(frame: CGRect) {
    super.init(frame: frame)

    self.backgroundColor = UIColor.white.withAlphaComponent(0.5)
    setupStack()
}

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

// MARK: - Setup Methods

func setupLabels() {
    let lbls = [firstLabel, secondLabel]
    for lbl in lbls {
        lbl.label.font = UIFont.preferredFont(forTextStyle: .title3)
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.heightAnchor.constraint(equalToConstant: 50).isActive = true
    }
    let pies = [firstPie, secondPie, thirdPie]
    for pie in pies {
        pie.translatesAutoresizingMaskIntoConstraints = false
        pie.heightAnchor.constraint(equalToConstant: 100).isActive = true
        pie.spanSuperView()  // spanSuperView is a UIView extension that I use
        pie.delegate = self as ChartViewDelegate
        // entry label styling
        pie.entryLabelColor = .white
        pie.entryLabelFont = .systemFont(ofSize: 12, weight: .light)
        pie.animate(xAxisDuration: 1.4, easingOption: .easeOutBack)
    }
}

func setupFake() {

    let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
    let unitsSold = [20.0, 4.0, 6.0, 3.0, 12.0, 16.0]

    setChart(dataPoints: months, values: unitsSold)
}

func setChart(dataPoints: [String], values: [Double]) {

    var dataEntries: [ChartDataEntry] = []

    for i in 0..<dataPoints.count {
        let dataEntry = ChartDataEntry(x: values[i], y: Double(i))
        dataEntries.append(dataEntry)
    }

    let set = PieChartDataSet(values: dataEntries, label: "Results")
    set.drawIconsEnabled = false
    set.sliceSpace = 2


    set.colors = ChartColorTemplates.joyful()
        + ChartColorTemplates.colorful()
        + ChartColorTemplates.liberty()
        + ChartColorTemplates.pastel()
        + [UIColor(red: 51/255, green: 181/255, blue: 229/255, alpha: 1)]

    let data = PieChartData(dataSet: set)
    let pies = [firstPie, secondPie, thirdPie]
    for pie in pies {
        pie.data = data
    }

}



func setupStack(){
    setupFake()
    setupLabels()
    let dashStack = UIStackView(arrangedSubviews: [firstPie, secondPie, thirdPie])
    dashStack.axis = .horizontal
    dashStack.distribution = .fillEqually
    dashStack.alignment = .center
    dashStack.spacing = 0
    dashStack.translatesAutoresizingMaskIntoConstraints = false


    let stackView = UIStackView(arrangedSubviews: [firstLabel, secondLabel, dashStack])
    stackView.axis = .vertical
    stackView.distribution = .fill
    stackView.alignment = .fill
    stackView.spacing = 0
    stackView.translatesAutoresizingMaskIntoConstraints = false

    self.addSubview(stackView)
    //autolayout the stack view
    stackView.spanSuperView()
}
}
jessi
  • 1,438
  • 1
  • 23
  • 36