0

This is probably easy, but I really can't find or understand how to get notified when a view goes to background and comes back using the SceneDelegate. If I have a viewController that goes into background by going back to main menu or something similar, how do I detect that via sceneWillResignActive?

I found here that one could do something like:

let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(appBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)

More specifically I have this ViewController.swift:

import Foundation
import UIKit
import AVKit

class IntroViewController :  UIViewController {
    
    @IBOutlet weak var videoView: UIView!
    @IBOutlet weak var introLabel: UILabel!
    var player: AVPlayer?

    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        playVideo()
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        NotificationCenter.default.removeObserver(self)
        if let player = player {
            player.pause()
        }
        player = nil
    }
    
    private func playVideo() {
        if player != nil {
            player!.play()
        } else {
            guard let path = Bundle.main.path(forResource: "intro_video", ofType:"mp4") else {
                debugPrint("intro.MP4 not found")
                return
            }
            player = AVPlayer(url: URL(fileURLWithPath: path))
            player!.actionAtItemEnd = .none
            let playerFrame = CGRect(x: 0, y: 0, width: videoView.frame.width, height: videoView.frame.height)
            
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(playerItemDidReachEnd(notification:)),
                                                   name: .AVPlayerItemDidPlayToEndTime,
                                                   object: player!.currentItem)
            
            let playerController = AVPlayerViewController()
            playerController.player = player
            playerController.videoGravity = .resizeAspectFill
            playerController.view.frame = playerFrame
            playerController.showsPlaybackControls = false
            addChild(playerController)
            videoView.addSubview(playerController.view)
            playerController.didMove(toParent: self)
            player!.play()
            player!.rate = 1.3
        }
    }
    
    @objc func playerItemDidReachEnd(notification: Notification) {
        let mainViewController = self.storyboard!.instantiateViewController(withIdentifier: "main_view_controller") as! MainViewController
        player!.pause()
        present(mainViewController, animated:true, completion:nil)
    }
    @IBAction func skipVideo(_ sender: Any) {
        if player != nil {
            player?.pause()
            player = nil
        }
        let mainViewController = self.storyboard!.instantiateViewController(withIdentifier: "main_view_controller") as! MainViewController
        present(mainViewController, animated:true, completion:nil)
    }
}

Then we have the SceneDelegate.swift:

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        // Called as the scene is being released by the system.
        // This occurs shortly after the scene enters the background, or when its session is discarded.
        // Release any resources associated with this scene that can be re-created the next time the scene connects.
        // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // Called when the scene has moved from an inactive state to an active state.
        // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // Called when the scene will move from an active state to an inactive state.
        // This may occur due to temporary interruptions (ex. an incoming phone call).
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // Called as the scene transitions from the background to the foreground.
        // Use this method to undo the changes made on entering the background.
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // Called as the scene transitions from the foreground to the background.
        // Use this method to save data, release shared resources, and store enough scene-specific state information
        // to restore the scene back to its current state.
    }


}

I want to be able to pause and restart the player when the user leaves the app. For starting the player something should somehow be called from sceneDidBecomeActive as I understand it. What I don't understand is how to in sceneDidBecomeActive that the viewcontroller that should be presented is the one I want.

sceneDidBecomeActive is (of course) called each time I go back to the app, no matter from which ViewController I left it.

In the link which refers to the old way, there seems to be some notification procedure to which you can assign a lambda. If I have a reference to the ViewController in SceneDelegate, I must somehow check and see if that is the one that should become active?

I don't find it supertrivial to follow the tutorials I have found about SceneDelegates. They mostly talk about differences between Appdelegate and what you could do, but never how to actually do it.

El_Loco
  • 1,716
  • 4
  • 20
  • 35
  • Can you please define what is "Going into background" means to you? Application as a whole goes to background and resumes when user taps on home button on iPhone or opens other application and comes back and thats what `AppDelegate` and `SceneDelegate` informs as well. But what you are specifying in question seems more like `viewWillDisappear` and `viewWillAppear` you can check `ViewController` life cycle for more detail https://stackoverflow.com/questions/5562938/looking-to-understand-the-ios-uiviewcontroller-lifecycle – Sandeep Bhandari Oct 21 '21 at 06:08

1 Answers1

0

You have the same resignActive, enterBackground in scene delegate as in app delegate. What you can do is have a reference to your view controller in sceneDelegate and call some method (you can define a protocole that view controller will implement) of the view controller when one of these scene delegate method is running.

Ptit Xav
  • 3,006
  • 2
  • 6
  • 15
  • How do I know which viewcontroller will be the active one? `sceneDidBecomeActive` is always called when I go back. I have more than one `ViewController` and I will only call the specific method if I am going back to that one. – El_Loco Oct 21 '21 at 10:28
  • You may find what you are looking for in this [post](https://stackoverflow.com/questions/6131205/how-to-find-topmost-view-controller-on-ios) – Ptit Xav Oct 21 '21 at 18:09