问题描述
我目前有draw(_ rect:CGRect)独立于表格视图工作(这意味着xy点都经过硬编码)。我正在尝试将此draw方法放入自定义UITableview中,以便它将在每个单元格中绘制单个图表。
在自定义表格视图中,有一个UIView与“ drawWorkoutChart”类相关联
import UIKit
let ftp = 100
class drawWorkoutChart: UIView {
override func draw(_ rect: CGRect) {
let dataPointsX: [CGFloat] = [0,10,16,18]
let dataPointsY: [CGFloat] = [0,55,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()
}
}
完成后,运行该应用程序将导致在UIView上绘制这些图表的倍数
我需要的帮助(在检查堆栈溢出/ YouTube和YouTube中的网页后)是我仍然不知道如何在cellForRowAt内调用它,以便可以替换dataPointX和dataPointY适当地设置,并根据输入数据以不同方式绘制每个单元格。
当前,UITableViewCell具有以下功能:
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!
}
}
谢谢。
-----------编辑---每个@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
}
}
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://stackoverflow.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)
}
}
最后绘制(_ rect)代码
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()))
UIColor.systemRed.setstroke()
ftpLine.linewidth = 2
ftpLine.stroke()
}
}
- IndexPath.row从cellForRowAt传递到
customTableCell视图(ErgWorkoutCell) - 在首次启动期间,打印语句输出这些信息
- 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]
-
由于某种原因,tableview仅获取最后一个indexPath.row 传递给draw(rect)函数,所有图表基本上都是 只需重复一张图表
-
一旦我开始滚动,则图表将重新填充到 正确的图表。
-
这是整个项目,可以使您更轻松地了解内容 继续 https://www.dropbox.com/s/3l7r7saqv0rhfem/uitableview-notupdating.zip?dl=0
解决方法
您只需要使数据点数组可作为公共属性来访问:
class DrawWorkoutChart: UIView {
private let ftp = 100
var dataPointsX: [CGFloat] = []
var dataPointsY: [CGFloat] = []
override func draw(_ rect: CGRect) {
super.draw(rect)
}
}
在您的自定义Cell类中,您需要一个自定义方法来将数据点传递到您的视图:
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 {
static let identifier = String(describing: ErgWorkoutCell.self)
lazy var chartView: DrawWorkoutChart = {
let chart = DrawWorkoutChart()
chart.translatesAutoresizingMaskIntoConstraints = false
chart.backgroundColor = .black
chart.clearsContextBeforeDrawing = true
let height = chart.heightAnchor.constraint(equalToConstant: 500)
height.priority = .defaultHigh
height.isActive = true
return chart
}()
func configure(dataPointsX: [CGFloat],dataPointsY: [CGFloat]) {
chartView.dataPointsX = dataPointsX
chartView.dataPointsY = dataPointsY
chartView.setNeedsDisplay()
}
override init(style: UITableViewCell.CellStyle,reuseIdentifier: String?) {
super.init(style: style,reuseIdentifier: reuseIdentifier)
contentView.fill(with: chartView)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
注意,我正在显式设置视图的backgroundColor和clearsContextBeforeDrawing属性。这很重要,这样可以在调用draw(rect :)之前清除图表。
configure方法就是神奇的地方。我们传入数据点并调用setNeedsDisplay,以便重新绘制视图。
现在在cellForRowAt方法中,您只需要传递数据点即可:
func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: ErgWorkoutCell.identifier) as! ErgWorkoutCell
cell.configure(
dataPointsX: .random(count: 6,in: 0..<20),dataPointsY: .random(count: 6,in: 0..<100)
)
return cell
}
这只是一种生成具有随机值的数组的方法:
extension Array where Element: SignedNumeric {
static func random(count: Int,in range: Range<Int>) -> Self {
return Array(repeating: 0,count: count)
.map { _ in Int.random(in: range) }
.compactMap { Element(exactly: $0) }
}
}
,
第一次显示视图或事件时调用此方法 发生使视图的可见部分无效的情况。你不应该 自己直接调用此方法。要使部分视图无效, 从而导致该部分被重绘,请调用setNeedsDisplay() 或使用setNeedsDisplay(_ :)方法。
为了使事情变得容易,我将声明一个看起来像这样的结构:
struct Point {
var x: Float
var y: Float
init(coordX: Float,coordY: Float) {
x = coordX
y = coordY
}
}
var dataPoints = [Point]()
然后我将使用您的json数组中的数据填充dataPoints,而不是创建两个单独的数组。
在自定义单元格类中,我将这样声明“设置”方法:
func setup(dataArray: [Point]) {
// setup customView with drawing
contentView.addSubview(customView)
}
并将代码从绘制移动到设置,可以从 cellForRowAt()调用此代码,
cell.setup(dataPoints)
希望您可以使用其中一些。