[iOS]自動再生無限スクロールビデオバナーを作成(1/2)
本当に機能を実現するのに1週間かかったようですが...
たくさんのバグがあって本当に...疲れた.
まず、ビデオを置かずに、画像だけを置いて、最後にビデオを置きます.
現在の3つの配列の一番前に元の最後の画像が表示されます.
一番後ろに最初の画像を入れます.
ピチュからのビューが欲しかったので、最初のスタートは1番から、
今index 3号の莱丘を右に歩いているときは?逆にすればいいんですよね?4番ピチュが出てきましたピューピュースライド1番ピコで移動すると、右に無限にスクロールできます.
https://medium.com/swlh/swift-make-infinite-scrolling-view-with-uicollectionview-cell-eedd2f9997a8
本来は集合ビューの代わりにスクロールビューで作成されていたのですが、最初はアプリを実行していたので、今は見えないカードの動画も自動で再生されるので、探してお勧めを探す!!見つけました.ベストリファレンス
タイミングはもう決まりました.次は自撮り棒でビデオを放送します!!入れます.ビデオを再生するために、AVPlayerを使用します.初めて利用したので、かなりうろうろしていましたが・・・
ビデオファイルをフォルダに入れて、資料を作って、ピチュビデオピカチュビデオ莱丘ビデオを出させます.
ここのファイルに従って貼り付けました.https://github.com/mobiraft/AutoPlayVideoInListExample
コレクションビューが過去になるにつれて、元の再生中のビデオは停止するために変数isplayingを作成します.
VideoIsMuted-マナーモード/音声モードでビデオ音声をオン/オフにする
AssetPlayer-ビデオを再生し、ビデオを戻して停止するために必要なPlayer
url-ビデオアドレス
残りは大体理解できるので、理解したいならゆっくり読んでください.(実は私にもわかりませんが…)
PlayerViewに参加し、PlayerViewを操作する方法を作成します.
コレクション・ビューでは、現在表示されているビデオの最初のもの(ページ化されたコードではなく、スクロール・コレクション・ビューで実装されたコード)です.カスタマイズしたいと思ったことはありますか?しかし、時間がかかる場合があります...1つの関数は、ビデオを再生するために使用され、もう1つは、停止した関数とビデオが画面内にあるかどうかを確認するために使用されます.
Video Cellを追加し、CellForItemAtを変更します.
1つ目から左にスクロールすると、0番のビデオが再生され、7番のビデオがスクロールされ、スクロールビデオDidScroll>playFirstVisibleVideoでは7番のビデオがキャプチャされず、再生や停止ができない現象が発生します.
だからscrollViewDidEndDeclaratingでscrollTooItemが発生してから遊んだことがありますが成功しませんでした...アニメーションを利用して終了時に実行することを実現しました.
たくさんのバグがあって本当に...疲れた.
1.横転コレクションビューを作成する
まず、ビデオを置かずに、画像だけを置いて、最後にビデオを置きます.
import UIKit
import SnapKit
class ViewController: UIViewController {
typealias VideoCell = VideoCollectionViewCell
typealias ImageCell = ImageCollectionViewCell
// 카드에 들어갈 이미지를 넣은 배열.
private var cardContents: [String] = ["0.jpg", "1.jpg", "2.jpg"]
lazy var collectionView: UICollectionView = {
// collection view layout setting
let layout = UICollectionViewFlowLayout.init()
layout.scrollDirection = .horizontal
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.footerReferenceSize = .zero
layout.headerReferenceSize = .zero
// collection view setting
let v = UICollectionView(frame: .zero, collectionViewLayout: layout)
v.isScrollEnabled = true
v.isPagingEnabled = true
v.showsHorizontalScrollIndicator = false
v.register(VideoCell.self, forCellWithReuseIdentifier: "VideoCell")
v.register(ImageCell.self, forCellWithReuseIdentifier: "ImageCell")
v.delegate = self
v.dataSource = self
// UI setting
v.backgroundColor = UIColor.black
v.layer.cornerRadius = 16
return v
}()
lazy var pageControl = UIPageControl()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.view.backgroundColor = UIColor(red: 227/255, green: 219/255, blue: 235/255, alpha: 1)
view.addSubview(collectionView)
view.addSubview(pageControl)
let edge = view.frame.width - 40
collectionView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.height.equalTo(edge)
}
pageControl.snp.makeConstraints { make in
make.top.equalTo(collectionView.snp.bottom).offset(10)
make.left.right.equalToSuperview()
}
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// page control 설정.
if scrollView.frame.size.width != 0 {
let value = (scrollView.contentOffset.x / scrollView.frame.width)
pageControl.currentPage = Int(round(value))
}
}
}
extension ViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
pageControl.numberOfPages = cardContents.count
return self.cardContents.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
cell.configure(image: cardContents[indexPath.item])
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: collectionView.frame.width, height: collectionView.frame.height)
}
}
Image Cellimport UIKit
class ImageCollectionViewCell: UICollectionViewCell {
private let imageView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFit
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.contentView.addSubview(imageView)
imageView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
func configure(image: String) {
if let image = UIImage(named: image) {
imageView.image = image
}
}
}
ここに実装すると、左と右がスクロール不可能な領域になります.左右を無限にスクロールさせましょう!
現在の3つの配列の一番前に元の最後の画像が表示されます.
一番後ろに最初の画像を入れます.
private var cardContents: [String] = ["2.jpg", "0.jpg", "1.jpg", "2.jpg", "0.jpg"]
これで先ほどと同じように動いていましたが、写真だけで5つのviewに増えてしまいました…ピチュからのビューが欲しかったので、最初のスタートは1番から、
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
collectionView.scrollToItem(at: [0, 1], at: .left, animated: false)
}
ピチュウから左に回ると、新しく追加されたインデックスは0番ライチュウ!!そしてシュッとしてからIndex 3号で移動すると!!左にスクロールすると無限スクロールが可能になります.(画像が同じなので、移動しているのは誰も見ていないので、ページコントロールは3つに変更せず、5つに保持されています.)今index 3号の莱丘を右に歩いているときは?逆にすればいいんですよね?4番ピチュが出てきましたピューピュースライド1番ピコで移動すると、右に無限にスクロールできます.
// 스크롤 뷰의 감속이 끝났을 때 == 스크롤뷰가 멈출 때 == 다음 페이지로 넘어갔을 때!!
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let value = (scrollView.contentOffset.x / scrollView.frame.width)
switch Int(round(value)) {
case 0:
let last = cardContents.count - 2
self.collectionView.scrollToItem(at: [0, last], at: .left, animated: false)
case cardContents.count - 1:
self.collectionView.scrollToItem(at: [0, 1], at: .left, animated: false)
default:
break
}
}
リファレンスhttps://medium.com/swlh/swift-make-infinite-scrolling-view-with-uicollectionview-cell-eedd2f9997a8
2.ビデオをセルに挿入
本来は集合ビューの代わりにスクロールビューで作成されていたのですが、最初はアプリを実行していたので、今は見えないカードの動画も自動で再生されるので、探してお勧めを探す!!見つけました.ベストリファレンス
タイミングはもう決まりました.次は自撮り棒でビデオを放送します!!入れます.ビデオを再生するために、AVPlayerを使用します.初めて利用したので、かなりうろうろしていましたが・・・
ビデオファイルをフォルダに入れて、資料を作って、ピチュビデオピカチュビデオ莱丘ビデオを出させます.
private var cardContents: [String] = ["picka.mov", "0.jpg", "picka.mov", "1.jpg", "picka.mov", "2.jpg", "picka.mov", "0.jpg"]
PlayerView/AVPlayayer+拡張/VideocollectionViewCellを作成します.ここのファイルに従って貼り付けました.https://github.com/mobiraft/AutoPlayVideoInListExample
セクションの貼り付け
AVPlayer+Extension
コレクションビューが過去になるにつれて、元の再生中のビデオは停止するために変数isplayingを作成します.
// AVPlayer+Extension
import Foundation
import AVKit
extension AVPlayer {
var isPlaying:Bool {
get {
return (self.rate != 0 && self.error == nil)
}
}
}
PlayerView
VideoIsMuted-マナーモード/音声モードでビデオ音声をオン/オフにする
AssetPlayer-ビデオを再生し、ビデオを戻して停止するために必要なPlayer
url-ビデオアドレス
残りは大体理解できるので、理解したいならゆっくり読んでください.(実は私にもわかりませんが…)
// PlayerView
import UIKit
import AVKit
class PlayerView: UIView {
static var videoIsMuted: Bool = true
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
private var assetPlayer:AVPlayer? {
didSet {
DispatchQueue.main.async {
if let layer = self.layer as? AVPlayerLayer {
layer.player = self.assetPlayer
}
}
}
}
private var playerItem:AVPlayerItem?
private var urlAsset: AVURLAsset?
var isMuted: Bool = true {
didSet {
self.assetPlayer?.isMuted = isMuted
}
}
var url: URL?
init() {
super.init(frame: .zero)
initialSetup()
}
required init?(coder: NSCoder) {
super.init(frame: .zero)
initialSetup()
}
private func initialSetup() {
if let layer = self.layer as? AVPlayerLayer {
layer.videoGravity = AVLayerVideoGravity.resizeAspect
}
}
func prepareToPlay(withUrl url:URL, shouldPlayImmediately: Bool = false) {
guard !(self.url == url && assetPlayer != nil && assetPlayer?.error == nil) else {
if shouldPlayImmediately {
play()
}
return
}
cleanUp()
self.url = url
let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
let urlAsset = AVURLAsset(url: url, options: options)
self.urlAsset = urlAsset
let keys = ["tracks"]
urlAsset.loadValuesAsynchronously(forKeys: keys, completionHandler: { [weak self] in
guard let strongSelf = self else { return }
strongSelf.startLoading(urlAsset, shouldPlayImmediately)
})
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}
private func startLoading(_ asset: AVURLAsset, _ shouldPlayImmediately: Bool) {
var error:NSError?
let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)
if status == AVKeyValueStatus.loaded {
let item = AVPlayerItem(asset: asset)
self.playerItem = item
self.assetPlayer = AVPlayer(playerItem: item)
self.didFinishLoading(self.assetPlayer, shouldPlayImmediately)
}
}
private func didFinishLoading(_ player: AVPlayer?, _ shouldPlayImmediately: Bool) {
guard let player = player, shouldPlayImmediately else { return }
DispatchQueue.main.async {
player.play()
}
}
@objc private func playerItemDidReachEnd(_ notification: Notification) {
guard notification.object as? AVPlayerItem == self.playerItem else { return }
DispatchQueue.main.async {
guard let videoPlayer = self.assetPlayer else { return }
videoPlayer.seek(to: .zero)
// videoPlayer.play() // 내가 생각한 카드뷰는 한번 재생하고 끝나면서 다음 카드로 넘어가고 하는거라 play 를 또 하면 영상이 겹쳐 들리는 문제가 발생해서 뺐다.
}
}
func play() {
guard self.assetPlayer?.isPlaying == false else { return }
DispatchQueue.main.async {
self.assetPlayer?.play()
}
}
func pause() {
guard self.assetPlayer?.isPlaying == true else { return }
DispatchQueue.main.async {
self.assetPlayer?.pause()
self.assetPlayer?.seek(to: .zero) // 여기도 셀을 떠났다가 해당 셀에 다시 들어가면 영상이 처음부터 실행되도록 하기 위해 변경.
}
}
func cleanUp() {
pause()
urlAsset?.cancelLoading()
urlAsset = nil
assetPlayer = nil
removeObservers()
}
func removeObservers() {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}
deinit {
cleanUp()
}
}
VideoCell
PlayerViewに参加し、PlayerViewを操作する方法を作成します.
import UIKit
class VideoCollectionViewCell: UICollectionViewCell {
private let playerView = PlayerView()
var url: URL?
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
self.contentView.addSubview(playerView)
playerView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
@objc
func volumeAction(_ sender:UIButton) {
sender.isSelected = !sender.isSelected
playerView.isMuted = sender.isSelected
PlayerView.videoIsMuted = sender.isSelected
}
func play() {
if let url = url {
playerView.prepareToPlay(withUrl: url, shouldPlayImmediately: true)
}
}
func pause() {
playerView.pause()
}
// 우리는 로컬 비디오를 재생할 것이므로, 이렇게!
func configure(_ file: String) {
let file = file.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: file[0], ofType: file[1]) else {
debugPrint( "\(file.joined(separator: ".")) not found")
return
}
let url = URL(fileURLWithPath: path)
self.url = url
playerView.prepareToPlay(withUrl: url, shouldPlayImmediately: false)
}
}
extension ViewController
コレクション・ビューでは、現在表示されているビデオの最初のもの(ページ化されたコードではなく、スクロール・コレクション・ビューで実装されたコード)です.カスタマイズしたいと思ったことはありますか?しかし、時間がかかる場合があります...1つの関数は、ビデオを再生するために使用され、もう1つは、停止した関数とビデオが画面内にあるかどうかを確認するために使用されます.
// ViewController
extension ViewController {
func playFirstVisibleVideo(_ shouldPlay:Bool = true) {
let cells = collectionView.visibleCells.sorted {
collectionView.indexPath(for: $0)?.item ?? 0 < collectionView.indexPath(for: $1)?.item ?? 0
}
let videoCells = cells.compactMap({ $0 as? VideoCollectionViewCell })
if videoCells.count > 0 {
let firstVisibileCell = videoCells.first(where: { checkVideoFrameVisibility(ofCell: $0) })
for videoCell in videoCells {
if shouldPlay && firstVisibileCell == videoCell {
videoCell.play()
}
else {
videoCell.pause()
}
}
}
}
func checkVideoFrameVisibility(ofCell cell: VideoCollectionViewCell) -> Bool {
var cellRect = cell.containerView.bounds
cellRect = cell.containerView.convert(cell.containerView.bounds, to: collectionView.superview)
return collectionView.frame.contains(cellRect)
}
}
ViewController
Video Cellを追加し、CellForItemAtを変更します.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if cardContents[indexPath.item].hasSuffix(".mov") {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "VideoCell", for: indexPath) as! VideoCell
cell.configure(video: cardContents[indexPath.item])
return cell
} else {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCell", for: indexPath) as! ImageCell
cell.configure(image: cardContents[indexPath.item])
return cell
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.frame.size.width != 0 {
let value = (scrollView.contentOffset.x / scrollView.frame.width)
pageControl.currentPage = Int(round(value))
}
playFirstVisibleVideo()
}
ここまで言うと、問題点は、うまく実施されているように見えますか...1つ目から左にスクロールすると、0番のビデオが再生され、7番のビデオがスクロールされ、スクロールビデオDidScroll>playFirstVisibleVideoでは7番のビデオがキャプチャされず、再生や停止ができない現象が発生します.
だからscrollViewDidEndDeclaratingでscrollTooItemが発生してから遊んだことがありますが成功しませんでした...アニメーションを利用して終了時に実行することを実現しました.
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let value = (scrollView.contentOffset.x / scrollView.frame.width)
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()
})
case cardContents.count - 1:
self.collectionView.scrollToItem(at: [0, 1], at: .left, animated: false)
default:
break
}
}
Reference
この問題について([iOS]自動再生無限スクロールビデオバナーを作成(1/2)), 我々は、より多くの情報をここで見つけました https://velog.io/@ddosang/iOS-자동-재생-무한스크롤-영상-배너-만들기-1テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol