图表实时更新图表 - 数据记录在一个 VC 上并绘制在另一个图表上

Charts update chart in realtime - data recorded on one VC and charted in another

我有一个选项卡式应用程序,它在一个选项卡上开始录音,并在另一个选项卡上绘制麦克风电平。

在第一个 VC 中,我正在收集麦克风电平并将它们存储在模型的数组中。我在模型中使用另一种方法来更新数据,我在第二个 VC 中调用它以更新视图。

我想做的是从第一个视图控制器更新第二个视图控制器中的图表(模型中存储数据的逻辑所在)


型号:

Chart.swift

import Charts

class Chart {
    static let sharedInstance = Chart()
    var lineChartView: LineChartView!

    func setChartValues() {
        let entries = (0..<GraphData.sharedInstance.array.count).map { (i) -> ChartDataEntry in
            let val = GraphData.sharedInstance.array[i]
            print(ChartDataEntry(x: Double(i), y: val))
            return ChartDataEntry(x: Double(i), y: val)
        }
        let set1 = LineChartDataSet(values: entries, label: "DataSet 1")
        let data = LineChartData(dataSet: set1)
        lineChartView.data = data
    }
}

GraphData.swift

class GraphData {
    static let sharedInstance = GraphData()
    var array = [Double]()
}

视图控制器:

第一个 VC:(每个评论的完整代码)

导入 UIKit 导入 AVFoundation

class SoundController: UIViewController, AVAudioRecorderDelegate {

    var recordingSession: AVAudioSession!
    var audioRecorder: AVAudioRecorder!
    var timer = Timer()
    @IBOutlet weak var errorLbl: UILabel!
    @IBOutlet weak var recordBtn: UIButton!

    @IBAction func recordButton(_ sender: UIButton) {
        if audioRecorder == nil {
            startRecording()
        } else {
            finishRecording(success: true)
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(false)
        errorLbl.text = ""
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        recordPermission()
    }

    func recordPermission() {
        recordingSession = AVAudioSession.sharedInstance()
        do {
            try recordingSession.setCategory(.playAndRecord, mode: .default)
            try recordingSession.setActive(true)
            recordingSession.requestRecordPermission() {  allowed in
                DispatchQueue.main.async {
                    if allowed {
                        print("recording allowed")
                    } else {
                        self.errorLbl.text = "Recording Permission was Denied. Please open settings and allow Cry It Out to access the microphone."
                    }
                }
            }
        } catch {
            self.errorLbl.text = "Recording Permission was Denied. Please open settings and allow the app to access the microphone."
        }
    }

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

    func startRecording() {
        if recordBtn.titleLabel?.text == "Tap to Re-record" {
            //reset values array
            GraphData.sharedInstance.array = []
        }
        let audioFilename = getDocumentsDirectory().appendingPathComponent("baby.m4a")

        let settings = [
            AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
            AVSampleRateKey: 12000,
            AVNumberOfChannelsKey: 1,
            AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
        ]

        do {
            audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
            audioRecorder.delegate = self
            audioRecorder.isMeteringEnabled = true
            runTimer()
            audioRecorder.record()
            runTimer()
            recordBtn.setTitle("Tap to Stop", for: .normal)
        } catch {
            finishRecording(success: false)
        }
    }

    func levelTimerCallback() -> Float {
        if audioRecorder != nil {
            audioRecorder.updateMeters()
            //If we are beyond a threshold value (-15)
            if audioRecorder.averagePower(forChannel: 0) > -15 {
                return audioRecorder.averagePower(forChannel: 0)
            }
        }
        return 0
    }

    func finishRecording(success: Bool) {
        //stop recording and reset recorder to nil for other checks
        audioRecorder.stop()
        audioRecorder = nil

        if success {
            recordBtn.setTitle("Tap to Re-record", for: .normal)
            if timer.isValid {
                timer.invalidate()
            }
        } else {
            //Recording Failed
            recordBtn.setTitle("Tap to Record", for: .normal)
            //disable timer if running (might be running or might not)
            if timer.isValid {
                timer.invalidate()
            }
        }
    }

    func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
        if !flag {
            finishRecording(success: false)
        }
    }

    //MARK: Timers

    @objc func updateTimer() {
        if levelTimerCallback() != 0 {
            let date = Date()
            let calendar = Calendar.current
            let month = calendar.component(.month, from: date)
            let day = calendar.component(.day, from: date)
            let hour = calendar.component(.hour, from: date)
            let minutes = calendar.component(.minute, from: date)
            let seconds = calendar.component(.second, from: date)
            let prettyDate = "\(month)/\(day) \(hour):\(minutes) and \(seconds) seconds"
            print(prettyDate)
            GraphData.sharedInstance.array.append(Double(levelTimerCallback()))
            //does this run the method? It should
            GraphController.sharedInstance.lineChartView?.data = Chart.sharedInstance.setChartValues()

        }
    }


    func runTimer() {
        timer = Timer.scheduledTimer(timeInterval: 1, target: self,   selector: (#selector(SoundController.updateTimer)), userInfo: nil, repeats: true)
    }

    func stopTimer() {
        timer.invalidate()
    }

}

第二个VC:

import UIKit
import Charts

class GraphController: UIViewController {
    static let sharedInstance = GraphController()
    @IBOutlet weak var lineChartView: LineChartView!


    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(true)
        self.lineChartView.data = Chart.sharedInstance.setChartValues()
    }

}

尝试不使用 lambda 函数的解决方案。您不需要使用 static 值。

1.准备你的 GraphController 以具有接收数据的功能

class GraphController: UIViewController {

    ...

    func dataReceived ( gData : GraphData ) {
        DispatchQueue.main.async {
            // Update your chart with gData
        }
    }

}

2。获取 GraphController 的参考并使用步骤 1 的功能进行更新。

请从选项卡中获取您的 GraphController 的引用,并使用此引用调用一个函数来更新您的图表。我不知道你的具体情况,但如果你有问题,请看这个:

class SoundController: UIViewController, AVAudioRecorderDelegate { 

    var graphController : GraphController?

    ...

    override func viewDidLoad() {
        super.viewDidLoad()
        ...
        // get graph controller reference from tabbar.
        self.graphController = self.tabBarController.viewControllers![INDEX_OF_VIEW_CONTROLLER] as! GraphController
    }

    // finally on your function call the function's graph controller receive data
    @objc func updateTimer() {
        if levelTimerCallback() != 0 {
            let date = Date()
            let calendar = Calendar.current
            let month = calendar.component(.month, from: date)
            let day = calendar.component(.day, from: date)
            let hour = calendar.component(.hour, from: date)
            let minutes = calendar.component(.minute, from: date)
            let seconds = calendar.component(.second, from: date)
            let prettyDate = "\(month)/\(day) \(hour):\(minutes) and \(seconds) seconds"
            print(prettyDate)
            GraphData.sharedInstance.array.append(Double(levelTimerCallback()))
            //does this run the method? It should
            //GraphController.sharedInstance.lineChartView?.data = Chart.sharedInstance.setChartValues()

             if graphController != nil {
                 self.graphController!.dataReceived( gData: GraphData.sharedInstance )
             } 


        }
    }

}

请查看代码,并根据需要进行一些更改,我已尝试尽可能自动化。