TIMEX
TIMEX

Reputation: 271584

How can I "remove" the embed segue in my container view, or remove the container view completely?

I have a splashViewController with a containerView, which is created in Storyboard.

In my story board, I automatically drag an embed segue from my containerView to a profileViewController.

Inside my splashViewController, I want to programatically "destroy" the containerView + profileViewController(both of them).

I've tried this:

self.containerView.hidden = true //obviously doesn't work. it's just visual
self.containerView.removeFromSuperView() //nope. it's just visual.

How can I remove both containerView and profileViewController completely, making sure that both deinit appropriately? If that's not possible, can I at least deinit the profileViewController? (I'll just set containerView as hidden).

Note: I play a movie video automatically (looping) in my profileViewController. It has sound, and even when I set containerView to nil and remove it from superview, the sound keeps playing.

Upvotes: 1

Views: 2151

Answers (3)

Kevin
Kevin

Reputation: 17536

I wrote up a quick example project here demonstrating the solution here. An autoplaying video (with dog barking) automatically plays in the bottom of the screen. Pressing the "Stop Player" button removes the child UIViewController and the container.

MPMoviePlayerController finished playing the audio even after the moviePlayer was stopped or set to nil (although not necessarily deallocated if an internal part of the MPMoviePlayerController held a strong reference which I suspect was the problem).

Anyway, I found a workaround by setting the contentURL of the movie player to nil on deinit before calling moviePlayer.stop()

//The `ProfileViewController` is created in the storyboard as a child of `ViewController`. 
class ViewController: UIViewController {

    @IBOutlet weak var container: UIView?

    @IBAction func stopPlayer(sender: UIButton) {            
        //We remove the child view controller (the ProfileViewController) from the parent
        for controller in self.childViewControllers {
            if let child = controller as? ProfileViewController {
                child.removeFromParentViewController() //This causes the deinit call
                container?.removeFromSuperview() //Clear up the container (stays as a black box otherwise)
            }
        }
    }
}

In the ProfileViewController, we have to change the contentURL and stop our movie player

class ProfileViewController: UIViewController {

    lazy var moviePlayer: MPMoviePlayerController = {
        let path = NSBundle.mainBundle().pathForResource("TestMovie", ofType: "mp4")!
        let moviePlayer = MPMoviePlayerController(contentURL: NSURL(fileURLWithPath: path)!)
        moviePlayer.movieSourceType = .File
        moviePlayer.prepareToPlay()
        moviePlayer.view.frame = self.view.frame
        return moviePlayer
    }()

    override func viewDidAppear(animated: Bool) {
        view.addSubview(moviePlayer.view)
        moviePlayer.play()

        //Looping
        NSNotificationCenter.defaultCenter().addObserver(self, selector: "moviePlayerDidFinish:", name:MPMoviePlayerPlaybackDidFinishNotification, object: moviePlayer)
    }

    //Change our contentURL then stop
    deinit {
        moviePlayer.contentURL = nil
        moviePlayer.stop()
    }

    @objc func moviePlayerDidFinish(notification: NSNotification) {
        let reasonInt = notification.userInfo![MPMoviePlayerPlaybackDidFinishReasonUserInfoKey] as! Int
        let reason = MPMovieFinishReason(rawValue: reasonInt)
        if (reason == MPMovieFinishReason.PlaybackEnded) {
            moviePlayer.play()
        }
    }
}

Tested on a iPhone 5 device and on the simulator

tl;dr

  1. Remove the container UIView itself by calling removeFromSuperview
  2. The MPMoviePlayerController keeps playing audio after being stopped, this is a bug. Workaround it by setting the contentURL then stopping

Upvotes: 3

bolnad
bolnad

Reputation: 4583

There are some details missing here, so I'll try to do my best, but it sounds like containerView is not the main view of that UIViewController, as I don't think you can say self.view.removeFromSuperView()

If you want to have a pattern where you delete the container view, I would recommend taking a look at how Apple suggests that you do this with their ContainerViewController There are a few more steps involved but it allows you to easily add and remove children. The downside is that you need to have a ViewController associated with each view.

The issue that it sounds like you are dealing with is this MPMoviePlayerController. MPMoviePlayerController requires a view associated with it. From Apple's documentation

Playback occurs in a view owned by the movie player and takes place either fullscreen or inline. You can incorporate a movie player’s view into a view hierarchy owned by your app, or use an MPMoviePlayerViewController object to manage the presentation for you.

My guess would be without looking at any code, that MPMoviePlayerController is hanging onto a reference to one of the view's that your deleting and its a strong reference so it isn't letting you actually delete that view and causing it to continue without being on screen.

It looks like MPMoviePlayerController is deprecated in ios9 so you should move to AVPlayer, which should give you more control over a movie inside your view

Upvotes: 1

Lucas Huang
Lucas Huang

Reputation: 4016

I think you should find a way to stop playing sounds. Also, you can just assign nil to those view controllers you want to deallocate. However, you can not do self = nil;.

Upvotes: -1

Related Questions