[iOS]自動再生無限スクロールビデオバナーを作成(2/2)


4.集合ビュータイマーの設定



5秒のすべてのカードを見せてくれれば
タイマーを5秒に設定し、5秒ごとにカードを移動します.(インデックスを追加)ただし、現在のページが最後のページである場合に最初のページに戻る必要があるため、nowPageという変数を作成して現在のページを保存する必要があります.
次のように関数を作ればいいです.
// 변수를 하나 추가하고,
private var nowPage: Int = 1

func setAutoCardTimer() {    
    let _: Timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { (Timer) in
        self.cardMove()
    }
}

func cardMove() {
    if nowPage == cardContents.count - 2 {
        collectionView.scrollToItem(at: [0, 1], at: .right, animated: false)
        nowPage = 1
        return
    }
    
    nowPage += 1
    collectionView.scrollToItem(at: [0, nowPage], at: .right, animated: true)
}
でも….私はすべてのカードに対して5秒ではありませんて、写真の3秒のビデオに対して13.2秒(ビデオの長さ)を表示したいです...ページを変更するたびに、異なるタイマーを設定します.
private var nowPage: Int = 1 {
    didSet {
        setAutoCardTimer()
    }
}

func setAutoCardTimer() {
    // timer 초기화 - 하지 않으면 이전의 타이머가 남아서 
    // 의도치 않은 타이밍에 카드가 넘어가는 현상이발생한다.
    timer?.invalidate()
    timer = nil
    
    // 영상의 종류가 하나라서 13.2 초로 통일했지만,
    // 영상도 여러 종류로 넣으려면 if - else 분기를 더 나누면 될 것 같다.
    // 이것보다 더 좋은 방법도 있을까??
    if cardContents[nowPage].hasSuffix("jpg") {
        timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { (Timer) in
            self.cardMove()
        }
    } else {
        timer = Timer.scheduledTimer(withTimeInterval: 13.2, repeats: false) { (Timer) in
            self.cardMove()
        }
    }
}

func cardMove() {
    if nowPage == cardContents.count - 2 {
        collectionView.scrollToItem(at: [0, 1], at: .right, animated: false)
        nowPage = 1
        return
    }
    
    nowPage += 1
    collectionView.scrollToItem(at: [0, nowPage], at: .right, animated: true)
}
また、カード移動時にnowPage変数も再設定する必要があるので、scrollViewDidEndDecelerationで設定しました.
extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.frame.size.width != 0 {
            let value = (scrollView.contentOffset.x / scrollView.frame.width)
            pageControl.currentPage = Int(round(value))
        }
        playFirstVisibleVideo()
    }
    
    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
        let value = (scrollView.contentOffset.x / scrollView.frame.width)
        nowPage = Int(round(value))
        
        switch Int(round(value)) {
        case 0:
            let last = cardContents.count - 2
            UIView.animate(withDuration: 0.01, animations: { [weak self] in
                self?.collectionView.scrollToItem(at: [0, last], at: .left, animated: false)
            }, completion: { [weak self] _ in
                self?.playFirstVisibleVideo()
                self?.nowPage = last
            })
        case cardContents.count - 1:
            collectionView.scrollToItem(at: [0, 1], at: .left, animated: false)
            nowPage = 1
        default:
            break
        }
    }
}

リファレンス


スクロールビューで画像ページhttps://fomaios.tistory.com/entry/Swift-スクロールビュー→画像→ページ→ページ付き画像→UIscrollViewにスクロール
ユニットに含まれるビデオのコレクションビューにビデオをスクロールさせる(ツイッターのように)
https://mobiraft.com/ios/for-gods-sake-can-you-autoplay-video-in-list-ios/
自動スクロールバナー-タイマー設定https://gonslab.tistory.com/24

4.ビデオ音声の重複の問題を修正します。


1.楽屋に行って帰ってきたとき


以上のようにすれば、アプリケーションを継続して実行する過程で、自動再生でもスクロール直接移動でも正常に動作し、ビデオを再生するときにバックグラウンドに行って見ると、ビデオ停止の問題が発生します.
だから、バックグラウンドに戻ってくるアクティビティを手配して、バックグラウンドに行くときにタイマーを初期化して、ビデオを停止して、それからフロントエンドに戻ってタイマーを設定して、ビデオを再生させます.
ここで重要なのは、最初にwillEnterForegroundを使ってforegroundをキャプチャし、アプリケーションをホームページにダウンロードして再実行する場合がキャプチャされますが、バックグラウンドアプリケーションリストに着いてからアプリケーションを実行するだけではキャプチャされません.2つのイベントをキャプチャするにはdidbecomeActiveを使用する必要があります.
// ViewController > viewDidLoad
// MARK: - Background Observer
        NotificationCenter.default.addObserver(self, selector: #selector(appMovedToForeground(_:)), name: UIApplication.didBecomeActiveNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(appMovedBackground), name: UIScene.willDeactivateNotification, object: nil)


// ViewController
    // 지금은 화면이 하나 뿐이라 will disappear 가 호출될 일은 없지만,
    // 만약 이 화면에서 새로운 화면을 present 하거나, pushVC 하게 되면,
    // 이 때도 초기화가 필요하다.
    override func viewWillDisappear(_ animated: Bool) {
        initCardView()
    }

    @objc func appMovedToForeground(_ notification: Notification) {
        setAutoCardTimer()
        playFirstVisibleVideo()
    }
    
    @objc func appMovedBackground(_ notification: Notification) {
        // code to execute
        initCardView()
    }
    
    func initCardView() {
//        collectionView.scrollToItem(at: IndexPath(item: 1, section: 0), at: .left, animated: false)
//        nowPage = 1
        timer?.invalidate()
        timer = nil
        playFirstVisibleVideo(false)
    }
このような音が重なるエラーがたくさんあります...
バックグラウンドで帰ってきた時には、もちろんビューウィルアプリも運行すると思っていたので.viewライフサイクル学習が急がれる...

2.初回ロード時


初めてビデオをロードすると、たまに0番に入った無限スクロールビデオが再生されます.プレイビューを無駄に呼び出すよりも画像を入れた方がよさそうなので、映像の先頭と同じ画像を0回入れ、処理後に画像を3秒ではない13.2秒タイマーをセットしました.
private var cardContents: [String] = ["picka.png", "0.jpg", "picka.mov", "1.jpg", "picka.mov", "2.jpg", "picka.mov", "0.jpg"]

func setAutoCardTimer() {
    timer?.invalidate()
    timer = nil
    
    if nowPage == 0 || cardContents[nowPage].hasSuffix("mov") {
        timer = Timer.scheduledTimer(withTimeInterval: 13.2, repeats: false) { (Timer) in
            self.cardMove()
        }
    } else {
        timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: false) { (Timer) in
            self.cardMove()
        }
    }
}

6.pageControl数量の調整

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.frame.size.width != 0 {
        let value = (scrollView.contentOffset.x / scrollView.frame.width)
        pageControl.currentPage = Int(round(value)) - 1
    }
    playFirstVisibleVideo()
}

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    pageControl.numberOfPages = cardContents.count - 2
    return self.cardContents.count
}

残りの問題は...

// PlayerView
private var assetPlayer: AVPlayer? {
    didSet {
        DispatchQueue.main.async {
            if let layer = self.layer as? AVPlayerLayer {
                layer.player = self.assetPlayer
            }
        }
    }
}
このアプリは他の画面の部分に切り替わっていないので再生されず、非常に間欠的なselfです.layerのselfに近づくとEXC BAD EXCESS KERN INVAID ADDRESSが発行されます...
  • はまずコミュニティに質問を提出し、PlayerViewのDenitの後、didSetが実行できるので、以下のように変更します.
  • // PlayerView
    private var deinited: Bool = false
    
    private var assetPlayer: AVPlayer? {
        didSet {
            if !deinited {
                DispatchQueue.main.async {
                    if let layer = self.layer as? AVPlayerLayer {
                        layer.player = self.assetPlayer
                    }
                }
            }
        }
    }
    
    deinit {
        deinited = true
        cleanUp() // clean up 안에 assetPlayer = nil 이 있음.
    }
    フルコード:https://github.com/ddosang/AutomaticPlayingVideoBanner
    エラーメッセージとより良い方法はコメントを通じて通報してください.😶‍🌫️