Search code examples
iosxcodeuiscrollviewswift3avaudioplayer

Change views on UIScrollView so music changes


I have a UIScrollView setup but when I change view the music doesn't stop. How do I make it so the music stops when you change view?

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear( animated)
    meditationState = .on
    setTrackForPlayerWith(trackName: "Bigsur")
    player.play()
    player.numberOfLoops = -1

}

Here is the whole class from viewcontroller.swift. I've added the func scrollViewDidScroll and the self part that you mentioned but it's still not working.

import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {

@IBOutlet weak var scrollView: UIScrollView!

override func viewDidLoad() {
    super.viewDidLoad()

    let vc0 = ViewController0(nibName: "ViewController0", bundle: nil)

    var frame0 = vc0.view.frame
    frame0.origin.x = self.view.frame.size.width
    vc0.view.frame = frame0

    self.addChildViewController(vc0)
    self.scrollView.addSubview(vc0.view)
    vc0.didMove(toParentViewController: self)

    let vc1 = ViewController1(nibName: "ViewController1", bundle: nil)

    var frame1 = vc1.view.frame
    frame1.origin.x = self.view.frame.size.width
    vc1.view.frame = frame1

    self.addChildViewController(vc1)
    self.scrollView.addSubview(vc1.view)
    vc1.didMove(toParentViewController: self)

    let vc2 = ViewController2(nibName: "ViewController2", bundle: nil)

    var frame2 = vc2.view.frame
    frame2.origin.x = self.view.frame.size.width * 2
    vc2.view.frame = frame2

    self.addChildViewController(vc2)
    self.scrollView.addSubview(vc2.view)
    vc2.didMove(toParentViewController: self)

    let vc3 = ViewController3(nibName: "ViewController3", bundle: nil)

    var frame3 = vc3.view.frame
    frame3.origin.x = self.view.frame.size.width * 3
    vc1.view.frame = frame3

    self.addChildViewController(vc3)
    self.scrollView.addSubview(vc3.view)
    vc3.didMove(toParentViewController: self)

    self.scrollView.contentSize = CGSize(width:     Double(self.view.frame.size.width * 4), height: Double(self.view.frame.size.height - 66))

    self.scrollView.delegate = self

  }

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.x > self.view.frame.size.x {
        player.stop()
    }
}

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

ViewController3 where 'player' is declared:

import UIKit
import AVFoundation

enum MeditationState {
    case on
    case off
}

class ViewController3: UIViewController {

    var player:AVAudioPlayer = AVAudioPlayer()
    var player1:AVAudioPlayer = AVAudioPlayer()
    var meditationState: MeditationState?
    var replicatorLayer = CAReplicatorLayer()
    var dot = CALayer()

    func updateTimer(){

        seconds += 1
        timerclock.text = "\(seconds)"
    }

    // Animation starts running

    func animation2() {

        // A layer that creates a specified number of copies of its sublayers (the source layer), each copy potentially having geometric, temporal, and color transformations applied to it.
        replicatorLayer = CAReplicatorLayer()

        // The layer’s bounds rectangle. Animatable.
        replicatorLayer.bounds = CGRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0)

        // The radius to use when drawing rounded corners for the layer’s background. Animatable.
        replicatorLayer.cornerRadius = 10.0

        // The background color of the receiver. Animatable.
        replicatorLayer.backgroundColor = UIColor(white: 0.0, alpha: 0.0).cgColor

        // The layer’s position in its superlayer’s coordinate space. Animatable.
        replicatorLayer.position = view.center

        // calling this method creates an array for that property and adds the specified layer to it.
        view.layer.addSublayer(replicatorLayer)


        // connectng the animation to the content

        // An object that manages image-based content and allows you to perform animations on that content
         dot = CALayer()

        // The layer’s bounds rectangle. Animatable.
        dot.bounds = CGRect(x: 0.0, y: 0.0, width: 12.0, height: 12.0)

        //The layer’s position in its superlayer’s coordinate space. Animatable.
        dot.position = CGPoint(x: 150.0, y: 40.0)

        //The background color of the receiver. Animatable.
        dot.backgroundColor = UIColor(white: 0.2, alpha: 1.0).cgColor

        // The color of the layer’s border. Animatable.
        dot.borderColor = UIColor(white: 1.0, alpha: 1.0).cgColor

        // The width of the layer’s border. Animatable.
        dot.borderWidth = 1.0

        //The radius to use when drawing rounded corners for the layer’s background. Animatable.
        dot.cornerRadius = 5.0


        //Appends the layer to the layer’s list of sublayers.
        replicatorLayer.addSublayer(dot)

        // number of copies of layer is instanceCount

        let nrDots: Int = 1000

        //The number of copies to create, including the source layers.
        replicatorLayer.instanceCount = nrDots

        // The basic type for floating-point scalar values in Core Graphics and related frameworks.
        let angle = CGFloat(2*M_PI) / CGFloat(nrDots)

        // The transform matrix applied to the previous instance to produce the current instance. Animatable.
        replicatorLayer.instanceTransform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0)

        // Type used to represent elapsed time in seconds.
        let duration: CFTimeInterval = 10.0

        // animation capabilities for a layer property.

        // An object that provides basic, single-keyframe animation capabilities for a layer property.
        let shrink = CABasicAnimation(keyPath: "transform.scale")

        // Defines the value the receiver uses to start interpolation.
        shrink.fromValue = 1.0

        // Defines the value the receiver uses to end interpolation.
        shrink.toValue = 0.1

        // Specifies the basic duration of the animation, in seconds.
        shrink.duration = duration

        // Determines the number of times the animation will repeat.
        shrink.repeatCount = Float.infinity

        // Add the specified animation object to the layer’s render tree.
        dot.add(shrink, forKey: "shrink")

        // Specifies the delay, in seconds, between replicated copies. Animatable.
        replicatorLayer.instanceDelay = duration/Double(nrDots)

        // The transform applied to the layer’s contents. Animatable.
        dot.transform = CATransform3DMakeScale(0.01, 0.01, 0.01)
    }

    // connecting the breathe in label

    @IBOutlet weak var label: UILabel!

    // instant delay

    @IBOutlet weak var instantDelay: UIButton!
    @IBAction func delayBtn(_ sender: Any) {

        dot.removeAnimation(forKey: "shrink")
        timer1.invalidate()
        seconds = 0
        timer2.invalidate()
        timerclock.text = "\(seconds)"
        time = 0
        timerLabel.text = "Breathe in"
        timerisOn = false
        pauseBtn.isHidden = true
        playBtn.isHidden = false

        label.isHidden = true
        replicatorLayer.isHidden = true
        instantDelay.isHidden = true
        instantDelay1.isHidden = false
        slider.isHidden = false
    }

    // Delay 1

    @IBOutlet weak var instantDelay1: UIButton!
    @IBAction func delayBtn1(_ sender: Any) {


        instantDelay1.isHidden = true
        instantDelay.isHidden = false
        label.isHidden = false
        slider.isHidden = true
    }


    //Slider for changing animation speed

    @IBOutlet weak var slider: UISlider!
    @IBAction func slider(_ sender: Any) {

    }

    @IBAction func speed(_ sender: UISlider) {
        view.layer.speed = sender.value

    }



    //Sound On button

    @IBOutlet weak var soundOn: UIButton!
    @IBAction func SoundOn(_ sender: Any) {
        meditationState = .on
    setTrackForPlayerWith(trackName: "Mute")
        player.play()
        soundoff.isHidden = false
        soundOn.isHidden = true
    }

    //Sound Off button

    @IBOutlet weak var soundoff: UIButton!
    @IBAction func SoundOff(_ sender: Any) {
        meditationState = .off
        setTrackForPlayerWith(trackName: "Bigsur")
        player.play()
        soundoff.isHidden = true
        soundOn.isHidden = false
    }

    //Timerclock at top of screen label

    @IBOutlet weak var timerclock: UILabel!

    // creating vars to set things

    var animation = CFTimeInterval()
    var timer1 = Timer()
    var timer2 = Timer()
    var time = 0
    var seconds = 0
    var timerisOn = false

    // connecting breathe in label

    @IBOutlet var question: UILabel!

    var arrayOfStrings: [String] = [""]

    // connecting timerclick and starting it
    @IBOutlet var timerLabel: UILabel!

    // changes the amount of time on the label of different labels
    func increaseTimer() {

        time += 1

        switch time {
        case 0 ... 7:
            timerLabel.text = "Hold"
        case 8 ... 10:
            timerLabel.text = "Breathe Out"
        case 11 ... 12:
            timerLabel.text = "Breathe in"
        default:
            time = 0
        }

    }

    // connecting the play button and vars
    @IBOutlet weak var playBtn: UIButton!
    @IBAction func play(sender: AnyObject) {

        bell(trackName: "Bell")
        player1.play()

        timer1 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(ViewController3.increaseTimer), userInfo: nil, repeats: true)
        pauseBtn.isHidden = false
        playBtn.isHidden = true
        if timerisOn == false {


        timer2 = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
            timerisOn = true
        }
        animation2()
    }


    // pausing the timer with the vars

    @IBOutlet weak var pauseBtn: UIButton!
    @IBAction func pause(sender: AnyObject) {
        dot.removeAnimation(forKey: "shrink")
        timer1.invalidate()
        seconds = 0
        timer2.invalidate()
        timerclock.text = "\(seconds)"
        time = 0
        timerLabel.text = "Breathe in"
        timerisOn = false
        pauseBtn.isHidden = true
        playBtn.isHidden = false

        }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear( animated)
        meditationState = .on
        setTrackForPlayerWith(trackName: "Bigsur")
        player.play()
        player.numberOfLoops = -1

    }

   override func viewDidLoad() {
        super.viewDidLoad()
    time += 1
        do {
            try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
            print("AVAudioSession Category Playback OK")
            do {
                try AVAudioSession.sharedInstance().setActive(true)
                print("AVAudioSession is Active")
            } catch let error as NSError {
                print(error.localizedDescription)
            }
        } catch let error as NSError {
            print(error.localizedDescription)
        }

    }

        func setTrackForPlayerWith(trackName: String) {
            do
            {
                let audioPath = Bundle.main.path(forResource: trackName, ofType: "mp3")
                try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
            }
            catch
            {
                //ERROR
            }
        }


    func bell(trackName: String) {
        do
        {
            let audioPath = Bundle.main.path(forResource: trackName, ofType: "mp3")
            try player1 = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
        }
        catch
        {
            //ERROR
        }
    }

        override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }



}

Solution

  • You can observe the changes made in your UIScrollView subclass in various delegate methods.

    First of all, lets make sure your scrollView's delegate is assigned to the viewController. To do so, one option is to add following to viewDidLoad()

    // `scrollView` should be whatever is your scrollView called in your VC
    self.scrollView.delegate = self
    

    Once this is done, lets make your UIViewController subclass conform to UIScrollViewDelegate

    class ViewController: UIViewController, UIScrollViewDelegate {
    
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            if scrollView.contentOffset.x > self.view.frame.size.x {
                player.stop()
            }
        }
    }
    

    UIScrollViewDelegate has numerous methods to observe changes in your scrollView. scrollViewDidScroll(_:) will be called every time there is an interaction with the scrollView, so as soon as the contentOffset is greater than the width of the view, lets stop the music.