I currently have draw(_ rect: CGRect) working independent from the tableview (meaning, the xy points are all hardcoded). I am trying to put this draw method into my custom UITableview such that it will draw the individual charts in each of the cell.
In the custom tableview, there is one UIView in which I have associated w/ the "drawWorkoutChart" class
import UIKit
let ftp = 100
class drawWorkoutChart: UIView {
override func draw(_ rect: CGRect) {
let dataPointsX: [CGFloat] = [0,10,10,16,16,18]
let dataPointsY: [CGFloat] = [0,55,73,73,52,52]
func point(at ix: Int) -> CGPoint {
let pointY = dataPointsY[ix]
let x = (dataPointsX[ix] / dataPointsX.max()!) * rect.width
let yMax = dataPointsY.max()! > 2*CGFloat(ftp) ? dataPointsY.max()! : 2*CGFloat(ftp)
let y = (1 - ((pointY - dataPointsY.min()!) / (yMax - dataPointsY.min()!))) * rect.height
// print("width:\(rect.width) height:\(rect.height) ix:\(ix) dataPoint:\(pointY) x:\(x) y:\(y) yMax:\(yMax)")
return CGPoint(x: x, y: y)
}
func drawFtpLine() -> CGFloat {
let yMax = dataPointsY.max()! > 2*CGFloat(ftp) ? dataPointsY.max()! : 2*CGFloat(ftp)
let ftpY = (CGFloat(ftp) / yMax ) * rect.height
return ftpY
}
//Here's how you make your curve...
let myBezier = UIBezierPath()
myBezier.move(to: CGPoint(x: 0, y: (1 - dataPointsY[0]) * rect.height))
for idx in dataPointsY.indices {
myBezier.addLine(to: point(at: idx))
}
// UIColor.systemBlue.setFill()
// myBezier.fill()
UIColor.systemBlue.setStroke()
myBezier.lineWidth = 3
myBezier.stroke()
let ftpLine = UIBezierPath()
ftpLine.move(to: CGPoint(x: 0, y: drawFtpLine()))
ftpLine.addLine(to: CGPoint(x: rect.width, y: drawFtpLine()))
UIColor.systemYellow.setStroke()
ftpLine.lineWidth = 2
ftpLine.stroke()
}
}
Once this is done, running the app will result in multiples of these charts drawn on the UIView
The help I need (after scouring thru stack overflow / YouTube and webpages from YouTube) is that I still do not know how to call it from within cellForRowAt such that I can replace the dataPointX and dataPointY appropriately and have it draw each cell differently based on the input data.
currently the UITableViewCell has this:
extension VCLibrary: UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jsonErgWorkouts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// this is cast AS! ErgWorkoutCell since this is a custom tableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "ergWorkoutCell") as? ErgWorkoutCell
cell?.ergWorkoutTitle.text = jsonErgWorkouts[indexPath.row].title
cell?.ergWorkoutTime.text = String(FormatDisplay.time(Int(jsonErgWorkouts[indexPath.row].durationMin * 60)))
cell?.ergValue.text = String(jsonErgWorkouts[indexPath.row].value)
cell?.ergWorkoutIF.text = String(jsonErgWorkouts[indexPath.row].intensity)
return cell!
}
}
Thanks.
----------- EDIT --- Update per @Robert C ----
UITableViewCell cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// this is cast AS! ErgWorkoutCell since this is a custom tableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "ergWorkoutCell") as! ErgWorkoutCell
cell.ergWorkoutTitle.text = jsonErgWorkouts[indexPath.row].title + "<><>" + String(jsonErgWorkouts[indexPath.row].id)
var tempX: [CGFloat] = []
var tempY: [CGFloat] = []
jsonErgWorkouts[indexPath.row].intervals.forEach {
tempX.append(CGFloat($0[0] * 60))
tempY.append(CGFloat($0[1]))
}
// I used ergIndexPassed as a simple tracking to see which cell is updating
cell.configure(ergX: tempX, ergY: tempY, ergIndexPassed: [indexPath.row])
// cell.ergLineChartView.setNeedsDisplay() // <- This doesn't make a diff
return cell
}
}
The CustomTableCell
import UIKit
extension UIView {
func fill(with view: UIView) {
addSubview(view)
NSLayoutConstraint.activate([
leftAnchor.constraint(equalTo: view.leftAnchor),
rightAnchor.constraint(equalTo: view.rightAnchor),
topAnchor.constraint(equalTo: view.topAnchor),
bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
class ErgWorkoutCell: UITableViewCell {
@IBOutlet weak var ergWorkoutTitle: UILabel!
@IBOutlet weak var ergWorkoutTime: UILabel!
@IBOutlet weak var ergWorkoutStress: UILabel!
@IBOutlet weak var ergWorkoutIF: UILabel!
@IBOutlet weak var ergLineChartView: UIView!
static let identifier = String(describing: ErgWorkoutCell.self)
// These code doesn't seem to Get triggered
lazy var chartView: DrawAllErgWorkoutChart = {
let chart = DrawAllErgWorkoutChart()
chart.translatesAutoresizingMaskIntoConstraints = false
chart.backgroundColor = .blue
chart.clearsContextBeforeDrawing = true
let height = chart.heightAnchor.constraint(equalToConstant: 500)
height.priority = .defaultHigh
height.isActive = true
return chart
}()
// I changed this to also accept the ergIndex
func configure(ergX: [CGFloat], ergY: [CGFloat], ergIndexPassed: [Int]) {
dataPointsX = ergX
dataPointsY = ergY
ergIndex = ergIndexPassed
// This works. ergLineChartView is the Embedded UIView in the customCell
ergLineChartView.setNeedsDisplay()
// These does nothing
// let chart = DrawAllErgWorkoutChart()
// chartView.setNeedsDisplay()
// chart.setNeedsDisplay()
print("ergWorkoutCell ergIndex:\(ergIndex)")//" DPY:\(dataPointsY)")
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.fill(with: chartView)
}
// https://stackguides.com/questions/38966565/fatal-error-initcoder-has-not-been-implemented-error-despite-being-implement
// required init?(coder: NSCoder) {
// fatalError("init(coder:) has not been implemented")
// }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Finally the draw(_ rect) code
import UIKit
// once I placed the dataPoints Array declaration here, then it works. This is a Global tho
var dataPointsX: [CGFloat] = []
var dataPointsY: [CGFloat] = []
var ergIndex: [Int] = []
class DrawAllErgWorkoutChart: UIView {
private let ftp = 100
override func draw(_ rect: CGRect) {
super.draw(rect)
let yMax = dataPointsY.max()! > 2*CGFloat(ftp) ? dataPointsY.max()! : 2*CGFloat(ftp)
print("DrawAllErgWorkoutChart ergIndex:\(ergIndex) ") //time:\(dataPointsX) watt:\(dataPointsY)")
func point(at ix: Int) -> CGPoint {
let pointY = dataPointsY[ix]
let x = (dataPointsX[ix] / dataPointsX.max()!) * rect.width
let y = (1 - (pointY / yMax)) * rect.height
return CGPoint(x: x, y: y)
}
func drawFtpLine() -> CGFloat {
let ftpY = (CGFloat(ftp) / yMax ) * rect.height
return ftpY
}
//Here's how you make your curve...
let myBezier = UIBezierPath()
let startPt = dataPointsY[0]
let nStartPt = (1 - (startPt / yMax)) * rect.height
// print("Cnt:\(ergLibCounter) StartPt:\(startPt) nStartPt:\(nStartPt)")
// myBezier.move(to: CGPoint(x: 0, y: (1 - dataPointsY[0]) * rect.height))
myBezier.move(to: CGPoint(x: 0, y: nStartPt))
for idx in dataPointsY.indices {
myBezier.addLine(to: point(at: idx))
}
UIColor.systemBlue.setStroke()
myBezier.lineWidth = 3
myBezier.stroke()
let ftpLine = UIBezierPath()
ftpLine.move(to: CGPoint(x: 0, y: drawFtpLine()))
ftpLine.addLine(to: CGPoint(x: rect.width, y: drawFtpLine()))
UIColor.systemRed.setStroke()
ftpLine.lineWidth = 2
ftpLine.stroke()
}
}
With the new Code above, what works:
- The IndexPath.row gets passed from cellForRowAt to the
customTableCell view (ErgWorkoutCell) - During initial launch, the print statements outputs these
- ergWorkoutCell ergIndex:[1]
- ergWorkoutCell ergIndex:[2]
- ergWorkoutCell ergIndex:[3]
- ergWorkoutCell ergIndex:[4]
- ergWorkoutCell ergIndex:[5]
- ergWorkoutCell ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
- DrawAllErgWorkoutChart ergIndex:[6]
for some reason, the tableview only gets the last indexPath.row passed to the draw(rect) function and all the charts are essentially just 1 chart, repeated
Once I start scrolling, then the charts gets re-populated to the correct charts.
Here is the entire project which may make it easier to see what's going on https://www.dropbox.com/s/3l7r7saqv0rhfem/uitableview-notupdating.zip?dl=0
@IBOutlet weak var ergLineChartView: UIView!
which was referencing an UIView! where instead this IBoutlet should be pointed to my drawRect function code. namely@IBOutlet weak var ergLineChartView: DrawAllErgWorkoutChart!
once this is done, then the values passed from cellForRowAt will get passed tofunc configure(ergX: [CGFloat], ergY: [CGFloat], ergIndexPassed: [Int])
which then passes it to DrawAllErgWorkoutChart! – app4g