You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过SceneDelegate监听ViewController前后台切换并控制AVPlayer启停

实现方案

核心逻辑

基于SceneDelegate的实现完全可以避开全局UIApplication通知,核心思路如下:

  • 首先自定义一个协议,规定前后台切换时需要执行的方法,所有需要感知Scene活跃状态的ViewController都可以遵守这个协议
  • 新增工具方法获取当前Scene的顶层显示ViewController
  • 在SceneDelegate的生命周期回调中,拿到顶层VC后判断是否遵守了自定义协议,执行对应逻辑即可
  • 不需要在SceneDelegate里硬编码判断ViewController类型,后续新增需要监听的页面只要遵守协议即可,维护性更高

具体代码实现

1. 定义前后台感知协议

新建一个Swift文件或者直接在IntroViewController上方添加如下代码:

@objc protocol SceneActivityAware: AnyObject {
    @objc optional func sceneWillResignActive()
    @objc optional func sceneDidBecomeActive()
    // 如果需要只监听真正进入后台/返回前台,可新增下面两个方法
    @objc optional func sceneDidEnterBackground()
    @objc optional func sceneWillEnterForeground()
}

2. 新增获取顶层ViewController的工具扩展

同样可以放在公共文件或者SceneDelegate文件上方:

extension UIWindow {
    func topMostViewController() -> UIViewController? {
        var top = self.rootViewController
        while let presentedViewController = top?.presentedViewController {
            top = presentedViewController
        }
        return top
    }
}

3. 修改SceneDelegate代码

完善对应的生命周期回调:

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        // 获取当前顶层VC,执行协议方法
        guard let topVC = window?.topMostViewController() as? SceneActivityAware else { return }
        topVC.sceneDidBecomeActive?()
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // 获取当前顶层VC,执行协议方法
        guard let topVC = window?.topMostViewController() as? SceneActivityAware else { return }
        topVC.sceneWillResignActive?()
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // 仅切回前台才触发的逻辑走这里
        guard let topVC = window?.topMostViewController() as? SceneActivityAware else { return }
        topVC.sceneWillEnterForeground?()
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // 仅真正进入后台才触发的逻辑走这里
        guard let topVC = window?.topMostViewController() as? SceneActivityAware else { return }
        topVC.sceneDidEnterBackground?()
    }
}

说明:如果你的需求是只要App失去焦点(比如来电、上拉控制中心)就暂停视频,就用sceneWillResignActive/sceneDidBecomeActive;如果只有真正切到后台再切回才需要处理,就用sceneDidEnterBackground/sceneWillEnterForeground,按需选择即可。

4. 修改IntroViewController代码

让它遵守SceneActivityAware协议,实现对应方法,同时删掉原来的全局通知监听相关代码:

import Foundation
import UIKit
import AVKit

// 遵守自定义协议
class IntroViewController :  UIViewController, SceneActivityAware {
    
    @IBOutlet weak var videoView: UIView!
    @IBOutlet weak var introLabel: UILabel!
    var player: AVPlayer?
    // 记录当前是否应该播放,避免页面未加载完成时误触发播放
    private var isPlayerReady = false

    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        playVideo()
        isPlayerReady = true
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        isPlayerReady = false
        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
        }
    }
    
    // MARK: - SceneActivityAware 协议实现
    func sceneWillResignActive() {
        guard isPlayerReady, let player = player else { return }
        player.pause()
    }
    
    func sceneDidBecomeActive() {
        guard isPlayerReady, let player = player else { return }
        player.play()
    }
    
    @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)
    }
}

问题解答

  1. 当ViewController因返回主菜单等场景进入后台时,sceneWillResignActive会先触发,此时通过工具方法拿到的顶层VC如果是当前的IntroViewController,就会自动调用它的sceneWillResignActive方法完成事件检测。
  2. 不需要在SceneDelegate里硬编码判断ViewController类型,通过协议的方式可以自动匹配所有需要感知状态的VC,后续新增其他需要监听的页面只要遵守SceneActivityAware协议实现对应方法即可,不需要修改SceneDelegate代码。

内容的提问来源于stack exchange,提问作者El_Loco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 04:54:01