LoginSignup
77

More than 5 years have passed since last update.

UIViewの背景に動画を流す簡単な方法

Last updated at Posted at 2018-03-31

iOSで背景に動画を流す簡単な方法

初回起動画面などで背景に動画が流れているアプリってよくありますよね。
そのようにボタンなどのUIの後ろで動画を流す簡単な方法を紹介します。

最小実装

import UIKit
import AVFoundation

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        // Bundle Resourcesからsample.mp4を読み込んで再生
        let path = Bundle.main.path(forResource: "sample", ofType: "mp4")!
        let player = AVPlayer(url: URL(fileURLWithPath: path))
        player.play()


        // AVPlayer用のLayerを生成
        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = view.bounds
        playerLayer.videoGravity = .resizeAspectFill
        playerLayer.zPosition = -1 // ボタン等よりも後ろに表示
        view.layer.insertSublayer(playerLayer, at: 0) // 動画をレイヤーとして追加
    }
}

demo

短い行で実現できましたね。

試しにラベルとボタンを置いてみます。

let label = UILabel(frame: CGRect(x: 0, y: 100, width: 200, height: 40))
label.text = "Hello!"
label.textColor = .white
label.font = UIFont.boldSystemFont(ofSize: 50)
label.textAlignment = .center
label.center.x = view.center.x
label.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
view.addSubview(label)

let loginButton = UIButton(frame: .init(x: 30, y: view.frame.height - 150, width: view.frame.width - 60, height: 50))
loginButton.setTitle("LOG IN", for: .normal)
loginButton.setTitleColor(.white, for: .normal)
loginButton.layer.borderWidth = 1
loginButton.layer.borderColor = UIColor.white.cgColor
loginButton.layer.cornerRadius = 4
loginButton.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
view.addSubview(loginButton)

let signupButton = UIButton(frame: loginButton.frame)
signupButton.frame.origin.y = loginButton.frame.minY - 60
signupButton.setTitle("SIGN UP", for: .normal)
signupButton.setTitleColor(.white, for: .normal)
signupButton.backgroundColor = UIColor(red: 0, green: 168.0/255, blue: 107.0/255, alpha: 1)
signupButton.layer.cornerRadius = 4
signupButton.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
view.addSubview(signupButton)

demo

はい、よく見る画面になりましたね。

これだけでも十分ですが、さらによくある以下の実装を追加してみます。

  • 動画を少し薄暗くする
  • リピート再生する
  • アプリをバックグラウンドにした後、フォアグラウンドにしても自動再生される (デフォルトでは停止してしまいます)
  • 端末を回転してもサイズが調整される

動画を薄暗くする & リピート再生 & アプリ復帰後自動再生 & 端末回転対応

class ViewController: UIViewController {

    private var observers: (player: NSObjectProtocol, 
                            willEnterForeground: NSObjectProtocol, 
                            bounds: NSKeyValueObservation)?

    override func viewDidLoad() {
        super.viewDidLoad()

        let path = Bundle.main.path(forResource: "sample", ofType: "mp4")!
        let player = AVPlayer(url: URL(fileURLWithPath: path))
        player.actionAtItemEnd = .none
        player.play()

        let playerLayer = AVPlayerLayer(player: player)
        playerLayer.frame = view.bounds
        playerLayer.videoGravity = .resizeAspectFill
        playerLayer.zPosition = -2 // 次に追加するoverlayより後ろにする
        view.layer.insertSublayer(playerLayer, at: 0)

        // 動画の上に重ねる半透明の黒いレイヤー
        let dimOverlay = CALayer()
        dimOverlay.frame = view.bounds
        dimOverlay.backgroundColor = UIColor.black.cgColor
        dimOverlay.zPosition = -1
        dimOverlay.opacity = 0.4 // 不透明度
        view.layer.insertSublayer(dimOverlay, at: 0)

        // 最後まで再生したら最初から再生する
        let playerObserver = NotificationCenter.default.addObserver(
            forName: .AVPlayerItemDidPlayToEndTime,
            object: player.currentItem,
            queue: .main) { [weak playerLayer] _ in
                playerLayer?.player?.seek(to: kCMTimeZero)
                playerLayer?.player?.play()
        }

        // アプリがバックグラウンドから戻ってきた時に再生する
        let willEnterForegroundObserver = NotificationCenter.default.addObserver(
            forName: .UIApplicationWillEnterForeground,
            object: nil,
            queue: .main) { [weak playerLayer] _ in
                playerLayer?.player?.play()
        }

        // 端末が回転した時に動画レイヤーのサイズを調整する
        let boundsObserver = view.layer.observe(\.bounds) { [weak playerLayer, weak dimOverlay] view, _ in
            DispatchQueue.main.async {
                playerLayer?.frame = view.bounds
                dimOverlay?.frame = view.bounds
            }
        }

        observers = (playerObserver, willEnterForegroundObserver, boundsObserver)
    }

    deinit {
        // 画面が破棄された時に監視をやめる
        if let observers = observers {
            NotificationCenter.default.removeObserver(observers.player)
            NotificationCenter.default.removeObserver(observers.willEnterForeground)
            observers.bounds.invalidate()
        }
    }
}

demo

※デモ動画ではボタンとラベルのコードも追加しています。

まとめ

意外と簡単に実現することが出来ました。
細かい制御を入れると少しコード量が増えるので、実際にはUIViewController内に記述せず、別のクラスに実装するのが良いと思います。

サンプルコードはこちらに置いてあります。
https://github.com/tattn/BackgroundVideoSample

関連URL

デモに使用した動画
http://mazwai.com/#/videos/218

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77