1

I have a tabbed app that starts recording on one tab, and plots the mic levels on another tab.

In the first VC, I'm gathering mic levels and storing them in an array in the model. I'm using another method in the model to update the data, and I'm calling it in the second VC in order to update the view.

What I want to do is update the chart in the second view controller from the first view controller (where the logic for storing data in the model is)


Model:

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]()
}

View Controllers:

First VC: (complete code per comment)

import UIKit import 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()
    }

}

Second 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()
    }

}
froggomad
  • 1,747
  • 2
  • 17
  • 40

1 Answers1

1

Try this solution without lambda functions. You don't need use static values.

1. Prepare your GraphController to have a function to receive data

class GraphController: UIViewController {

    ...

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

}

2. Get the reference of GraphController and use the function of step 1 to make your updates.

Please, get the reference of your GraphController from tab and use this reference to call a function to make your chart updates. I don't know exactely your situation, but if you have problems to make it, please look this: https://stackoverflow.com/a/39499751/5140756

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 )
             } 


        }
    }

}

Please, look the code, and make some changes that you need, I tried automate the max that I can.

Augusto
  • 3,825
  • 9
  • 45
  • 93