微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何从cellForRowAt调用draw_ rect:CGRect在自定义tableview上绘制?

如何解决如何从cellForRowAt调用draw_ rect:CGRect在自定义tableview上绘制?

我目前有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上绘制这些图表的倍数

PIC: The UIVIew Chart that gets drawn on each cell in the tableview (Not enough reputation for it to show inline

我需要的帮助(在检查堆栈溢出/ 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()
    
  }
}

使用上面的新代码,有效的方法

  1. IndexPath.row从cellForRowAt传递到
    customTableCell视图(ErgWorkoutCell)
  2. 在首次启动期间,打印语句输出这些信息
 - 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]
  1. 由于某种原因,tableview仅获取最后一个indexPath.row 传递给draw(rect)函数,所有图表基本上都是 只需重复一张图表

  2. 一旦我开始滚动,则图表将重新填充到 正确的图表。

  3. 这是整个项目,可以使您更轻松地了解内容 继续 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) }
    }
}
,

来自documentation

第一次显示视图或事件时调用此方法 发生使视图的可见部分无效的情况。你不应该 自己直接调用此方法。要使部分视图无效, 从而导致该部分被重绘,请调用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)

希望您可以使用其中一些。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。