BadCodeDeveloper
BadCodeDeveloper

Reputation: 475

How to pass IBOutlet between two classes?

I have a class for managing AVPlayer and ViewController showing AVPlayer. In ViewController I have 3 IBOutlets:

@IBOutlet weak var playButton: UIButton!
@IBOutlet weak var progressBar: UIProgressView!
@IBOutlet weak var timerLabel: UILabel!

The problem is I can't figure out how to pass IBOutlets from ViewController to my player managing class. I always get nil and

fatal error: unexpectedly found nil while unwrapping an Optional value

when initialising progressBar, timerLabel or playButton.

Here is some code from my player managing class:

import UIKit
import AVFoundation

class RecordsAudioPlayer: NSObject
{
    var player = AVPlayer()
    var timerUpdateAudioProgressView = Timer()
    var timerUpdateTime = Timer()
    var isPlaying: Bool = false

...

func updateAudioProgressView()
    {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "callRecords") as! CallRecordsViewController

        let progressBar = vc.progressBar! 
        print("method fired: updateAudioProgressView ", NSDate.init(timeIntervalSinceNow: 0).description)
        if isPlaying
        {
            let currentPlayerItem = player.currentItem
            let duration = Float((currentPlayerItem?.asset.duration.value)!)/Float((currentPlayerItem?.asset.duration.timescale)!)
            let currentTime = Float(player.currentTime().value)/Float(player.currentTime().timescale)
            progressBar.setProgress(Float(currentTime/duration), animated: false)
        }
    }

    func updateTime()
    {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "callRecords") as! CallRecordsViewController
        let timerLabel: UILabel! = vc.timerLabel! //***I get exception here
        print("method fired: updateTime ", NSDate.init(timeIntervalSinceNow: 0).description)
        let currentTime = Int64(player.currentTime().value)/Int64(player.currentTime().timescale)
        let minutes = currentTime / 60
        let seconds = currentTime - minutes * 60
        timerLabel.text = String(format: "%02d : %02d", Int(minutes), Int(seconds))
    }

Upvotes: 0

Views: 637

Answers (2)

Ravi
Ravi

Reputation: 2451

In this case delegates came for our rescue. Create delegate methods for your RecordsAudioPlayer class. And use them to update progress bar and timer label.

protocol RecordsAudioPlayerDelegate
{
  func audioplayerProgressUpdated(time:CGFloat) // change the method as your wish
}

class RecordsAudioPlayer: NSObject
{
  var delegate:RecordsAudioPlayerDelegate?
  var player = AVPlayer()
  var timerUpdateAudioProgressView = Timer()
  var timerUpdateTime = Timer()
  var isPlaying: Bool = false
    ...
    ...
    ...

  //Call the delegate method from where you get the player current time
  self.delegate.audioplayerProgressUpdated(playerTime)

  //For controlling play/pause using play button 
  func playButtonClicked()
  {
    //play/pause player based on player status
  }
}

And inside your ViewController while/after creating RecordsAudioPlayer object set delegate as self

class YourViewController : UIViewController, RecordsAudioPlayerDelegate
{
  var recordsAudioPlayer : RecordsAudioPlayer!

  // set delegate where you create object for RecordsAudioPlayer
  self.recordsAudioPlayer.delegate = self

  //Now implement the delegate function
  func audioplayerProgressUpdated(time:CGFloat)
  {
   //update progress view and timer label
  }

  //And inside from the play button action method 
  @IBAction func playButtonClicked(sender: UIButton)
  {
   self.recordsAudioPlayer.playButtonClicked()
  }

}

Upvotes: 1

Andreas Oetjen
Andreas Oetjen

Reputation: 10209

Outlet connections get established as soon as the view is loaded - and this is typically deferred until the view is being presented.

You can force this by calling vc.loadViewIfNeeded() (where vc is your view controller)

But not quite shure if your implementation of updateAudioProgressView and updateTime really do what you expect: They always create new view controllers and update their subviews/outlets. These newly created view controllers don't even get displayed (or you just skipped the presentation code). If your intention was to update an existing view controller, you would have to do something else (e.g. hand in the view controller to be updated).

And just a remark: It is highly recommended that you do not do the things the way you do it. The view controller should be the only one to update it's outlets. Anyone else should just update properties of the view controller (like setting the current progress, time value etc.) and then inform the view controller to update itself. The view controller will then update it's presenting outlets the way it wants.

Upvotes: 1

Related Questions