滚动 CollectionView 期间不必要地打开 YouTube 播放器
YouTube player opening unnecessarily during scrolling of CollectionView
我正在开发一个聊天机器人,其中不同类型的响应来自服务器,我在聊天屏幕中使用 UICollectionView
单元格显示响应。根据服务器响应显示不同类型的单元格。当服务器响应播放视频时,我会显示包含 youtube 播放器的单元格。我正在使用 https://github.com/kieuquangloc147/YouTubePlayer-Swift。问题是当我滚动聊天屏幕 (collectionView) 时,youtube 播放器一次又一次打开。有时它会阻止所有 UI 元素并停止滚动。我尝试了不同的方法,但无法解决。这是代码:
PlayerView
:
import UIKit
class PlayerView: UIView, YouTubePlayerDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
addYotubePlayer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// youtube player
lazy var youtubePlayer: YouTubePlayerView = {
let viewFrame = UIScreen.main.bounds
let player = YouTubePlayerView(frame: CGRect(x: 0, y: 0, width: viewFrame.width - 16, height: viewFrame.height * 1/3))
player.delegate = self
return player
}()
// used as an overlay to dismiss the youtube player
let blackView = UIView()
// youtube player loader
lazy var playerIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView()
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.hidesWhenStopped = true
return indicator
}()
// shows youtube player
func addYotubePlayer() {
if let window = UIApplication.shared.keyWindow {
blackView.frame = window.frame
self.addSubview(blackView)
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tap.numberOfTapsRequired = 1
tap.cancelsTouchesInView = false
blackView.addGestureRecognizer(tap)
let centerX = UIScreen.main.bounds.size.width / 2
let centerY = UIScreen.main.bounds.size.height / 2
blackView.addSubview(playerIndicator)
playerIndicator.center = CGPoint(x: centerX, y: centerY)
playerIndicator.startAnimating()
blackView.addSubview(youtubePlayer)
youtubePlayer.center = CGPoint(x: centerX, y: centerY)
blackView.alpha = 0
youtubePlayer.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.youtubePlayer.alpha = 1
}, completion: nil)
}
}
func play(_ videoID: String) {
youtubePlayer.loadVideoID(videoID)
}
@objc func handleDismiss() {
blackView.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
UIApplication.shared.keyWindow?.removeFromSuperview()
}
func playerReady(_ videoPlayer: YouTubePlayerView) {
self.playerIndicator.stopAnimating()
}
func playerStateChanged(_ videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
}
func playerQualityChanged(_ videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
}
}
YouTubePlayerCell
(我在 collectionView
母鸡服务器响应视频时展示):
import UIKit
class YouTubePlayerCell: ChatMessageCell {
var player: PlayerView = PlayerView(frame: UIScreen.main.bounds)
override func setupViews() {
super.setupViews()
setupCell()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect.zero
}
func loadVideo(with videoID: String) {
player.tag = 24
UIApplication.shared.keyWindow?.addSubview(player)
player.play(videoID)
}
override func prepareForReuse() {
super.prepareForReuse()
player.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
}
}
下面是我在 UICollectionView
的 cellForItemAt
方法中介绍 YouTubePlayerCell
的方式
let message = messages[indexPath.row]
if message.actionType == ActionType.video_play.rawValue {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell {
self.resignResponders()
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
return cell
}
}
完整的源代码可以在这里找到:https://github.com/imjog/susi_iOS/tree/ytplayer
我可以在下面的代码中看到
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
您正在调用 loadVideo 方法,该方法负责显示播放器。
因此,在滚动时您正在重复使用该单元格,它会调用 loadVideo 方法并呈现播放器。所以解决方案是在显示单元格时不默认开始播放视频,在单元格视频叠加层上提供一个 play/pause 按钮,然后单击该按钮开始播放视频。
如果我的分析有误,请告诉我您遇到的具体问题。
为什么每次要播放视频时都将播放器添加为子视图?我的建议是,当您在整个屏幕上添加播放器视图时,您可以只有一个视图实例并仅添加一次(可能在开始时)并将其隐藏。要播放视频,只需取消隐藏播放器并加载视频。
相反,最佳做法是为 Youtube 播放器设置一个视图控制器,并在您每次需要播放时向其显示视频 ID,然后在播放完毕后关闭。
感谢您的回答。我是这样解决的:
我现在不是在单元格上显示播放器设置,而是在单元格中添加一个缩略图并在缩略图视图上添加一个按钮,这样每当用户单击 play 按钮时,它打开一个新控制器(之前我在 UIWindow
中呈现)并使用协议将其呈现为 overFullScreen
的 modalPresentationStyle
,因为单元格无法呈现 ViewController.
协议:(在 YouTubePlayerCell class)
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
决赛 YouTubePlayer.swift
:
import UIKit
import Kingfisher
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
class YouTubePlayerCell: ChatMessageCell {
weak var delegate: PresentControllerDelegate?
var message: Message? {
didSet {
addThumbnail()
}
}
lazy var thumbnailView: UIImageView = {
let imageView = UIImageView()
imageView.image = ControllerConstants.Images.placeholder
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 15
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var playButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(ControllerConstants.Images.youtubePlayButton, for: .normal)
button.addTarget(self, action: #selector(playVideo), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func setupViews() {
super.setupViews()
setupCell()
prepareForReuse()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect(x: 8, y: 0, width: 208, height: 158)
textBubbleView.layer.borderWidth = 0.2
textBubbleView.backgroundColor = .white
}
override func prepareForReuse() {
super.prepareForReuse()
thumbnailView.image = nil
}
func addThumbnail() {
textBubbleView.addSubview(thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "H:|-4-[v0]-4-|", views: thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: thumbnailView)
self.downloadThumbnail()
self.addPlayButton()
}
func addPlayButton() {
thumbnailView.addSubview(playButton)
playButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
playButton.widthAnchor.constraint(equalToConstant: 44).isActive = true
playButton.centerXAnchor.constraint(equalTo: thumbnailView.centerXAnchor).isActive = true
playButton.centerYAnchor.constraint(equalTo: thumbnailView.centerYAnchor).isActive = true
}
func downloadThumbnail() {
if let videoID = message?.videoData?.identifier {
let thumbnailURLString = "https://img.youtube.com/vi/\(videoID)/default.jpg"
let thumbnailURL = URL(string: thumbnailURLString)
thumbnailView.kf.setImage(with: thumbnailURL, placeholder: ControllerConstants.Images.placeholder, options: nil, progressBlock: nil, completionHandler: nil)
}
}
@objc func playVideo() {
if let videoID = message?.videoData?.identifier {
let playerVC = PlayerViewController(videoID: videoID)
playerVC.modalPresentationStyle = .overFullScreen
delegate?.loadNewScreen(controller: playerVC)
}
}
}
在集合ViewController中委托实现:
extension ChatViewController: PresentControllerDelegate {
func loadNewScreen(controller: UIViewController) {
self.present(controller, animated: true, completion: nil)
}
}
最终源代码 可在此处找到:https://github.com/fossasia/susi_iOS/pull/372
我正在开发一个聊天机器人,其中不同类型的响应来自服务器,我在聊天屏幕中使用 UICollectionView
单元格显示响应。根据服务器响应显示不同类型的单元格。当服务器响应播放视频时,我会显示包含 youtube 播放器的单元格。我正在使用 https://github.com/kieuquangloc147/YouTubePlayer-Swift。问题是当我滚动聊天屏幕 (collectionView) 时,youtube 播放器一次又一次打开。有时它会阻止所有 UI 元素并停止滚动。我尝试了不同的方法,但无法解决。这是代码:
PlayerView
:
import UIKit
class PlayerView: UIView, YouTubePlayerDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
addYotubePlayer()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// youtube player
lazy var youtubePlayer: YouTubePlayerView = {
let viewFrame = UIScreen.main.bounds
let player = YouTubePlayerView(frame: CGRect(x: 0, y: 0, width: viewFrame.width - 16, height: viewFrame.height * 1/3))
player.delegate = self
return player
}()
// used as an overlay to dismiss the youtube player
let blackView = UIView()
// youtube player loader
lazy var playerIndicator: UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView()
indicator.activityIndicatorViewStyle = .whiteLarge
indicator.hidesWhenStopped = true
return indicator
}()
// shows youtube player
func addYotubePlayer() {
if let window = UIApplication.shared.keyWindow {
blackView.frame = window.frame
self.addSubview(blackView)
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss))
tap.numberOfTapsRequired = 1
tap.cancelsTouchesInView = false
blackView.addGestureRecognizer(tap)
let centerX = UIScreen.main.bounds.size.width / 2
let centerY = UIScreen.main.bounds.size.height / 2
blackView.addSubview(playerIndicator)
playerIndicator.center = CGPoint(x: centerX, y: centerY)
playerIndicator.startAnimating()
blackView.addSubview(youtubePlayer)
youtubePlayer.center = CGPoint(x: centerX, y: centerY)
blackView.alpha = 0
youtubePlayer.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.blackView.alpha = 1
self.youtubePlayer.alpha = 1
}, completion: nil)
}
}
func play(_ videoID: String) {
youtubePlayer.loadVideoID(videoID)
}
@objc func handleDismiss() {
blackView.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
UIApplication.shared.keyWindow?.removeFromSuperview()
}
func playerReady(_ videoPlayer: YouTubePlayerView) {
self.playerIndicator.stopAnimating()
}
func playerStateChanged(_ videoPlayer: YouTubePlayerView, playerState: YouTubePlayerState) {
}
func playerQualityChanged(_ videoPlayer: YouTubePlayerView, playbackQuality: YouTubePlaybackQuality) {
}
}
YouTubePlayerCell
(我在 collectionView
母鸡服务器响应视频时展示):
import UIKit
class YouTubePlayerCell: ChatMessageCell {
var player: PlayerView = PlayerView(frame: UIScreen.main.bounds)
override func setupViews() {
super.setupViews()
setupCell()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect.zero
}
func loadVideo(with videoID: String) {
player.tag = 24
UIApplication.shared.keyWindow?.addSubview(player)
player.play(videoID)
}
override func prepareForReuse() {
super.prepareForReuse()
player.removeFromSuperview()
UIApplication.shared.keyWindow?.viewWithTag(24)?.removeFromSuperview()
}
}
下面是我在 UICollectionView
cellForItemAt
方法中介绍 YouTubePlayerCell
的方式
let message = messages[indexPath.row]
if message.actionType == ActionType.video_play.rawValue {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ControllerConstants.youtubePlayerCell, for: indexPath) as? YouTubePlayerCell {
self.resignResponders()
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
return cell
}
}
完整的源代码可以在这里找到:https://github.com/imjog/susi_iOS/tree/ytplayer
我可以在下面的代码中看到
if let videoId = message.videoData?.identifier {
cell.loadVideo(with: videoId)
}
您正在调用 loadVideo 方法,该方法负责显示播放器。 因此,在滚动时您正在重复使用该单元格,它会调用 loadVideo 方法并呈现播放器。所以解决方案是在显示单元格时不默认开始播放视频,在单元格视频叠加层上提供一个 play/pause 按钮,然后单击该按钮开始播放视频。 如果我的分析有误,请告诉我您遇到的具体问题。
为什么每次要播放视频时都将播放器添加为子视图?我的建议是,当您在整个屏幕上添加播放器视图时,您可以只有一个视图实例并仅添加一次(可能在开始时)并将其隐藏。要播放视频,只需取消隐藏播放器并加载视频。
相反,最佳做法是为 Youtube 播放器设置一个视图控制器,并在您每次需要播放时向其显示视频 ID,然后在播放完毕后关闭。
感谢您的回答。我是这样解决的:
我现在不是在单元格上显示播放器设置,而是在单元格中添加一个缩略图并在缩略图视图上添加一个按钮,这样每当用户单击 play 按钮时,它打开一个新控制器(之前我在 UIWindow
中呈现)并使用协议将其呈现为 overFullScreen
的 modalPresentationStyle
,因为单元格无法呈现 ViewController.
协议:(在 YouTubePlayerCell class)
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
决赛 YouTubePlayer.swift
:
import UIKit
import Kingfisher
protocol PresentControllerDelegate: class {
func loadNewScreen(controller: UIViewController) -> Void
}
class YouTubePlayerCell: ChatMessageCell {
weak var delegate: PresentControllerDelegate?
var message: Message? {
didSet {
addThumbnail()
}
}
lazy var thumbnailView: UIImageView = {
let imageView = UIImageView()
imageView.image = ControllerConstants.Images.placeholder
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 15
imageView.isUserInteractionEnabled = true
return imageView
}()
lazy var playButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(ControllerConstants.Images.youtubePlayButton, for: .normal)
button.addTarget(self, action: #selector(playVideo), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override func setupViews() {
super.setupViews()
setupCell()
prepareForReuse()
}
func setupCell() {
messageTextView.frame = CGRect.zero
textBubbleView.frame = CGRect(x: 8, y: 0, width: 208, height: 158)
textBubbleView.layer.borderWidth = 0.2
textBubbleView.backgroundColor = .white
}
override func prepareForReuse() {
super.prepareForReuse()
thumbnailView.image = nil
}
func addThumbnail() {
textBubbleView.addSubview(thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "H:|-4-[v0]-4-|", views: thumbnailView)
textBubbleView.addConstraintsWithFormat(format: "V:|-4-[v0]-4-|", views: thumbnailView)
self.downloadThumbnail()
self.addPlayButton()
}
func addPlayButton() {
thumbnailView.addSubview(playButton)
playButton.heightAnchor.constraint(equalToConstant: 44).isActive = true
playButton.widthAnchor.constraint(equalToConstant: 44).isActive = true
playButton.centerXAnchor.constraint(equalTo: thumbnailView.centerXAnchor).isActive = true
playButton.centerYAnchor.constraint(equalTo: thumbnailView.centerYAnchor).isActive = true
}
func downloadThumbnail() {
if let videoID = message?.videoData?.identifier {
let thumbnailURLString = "https://img.youtube.com/vi/\(videoID)/default.jpg"
let thumbnailURL = URL(string: thumbnailURLString)
thumbnailView.kf.setImage(with: thumbnailURL, placeholder: ControllerConstants.Images.placeholder, options: nil, progressBlock: nil, completionHandler: nil)
}
}
@objc func playVideo() {
if let videoID = message?.videoData?.identifier {
let playerVC = PlayerViewController(videoID: videoID)
playerVC.modalPresentationStyle = .overFullScreen
delegate?.loadNewScreen(controller: playerVC)
}
}
}
在集合ViewController中委托实现:
extension ChatViewController: PresentControllerDelegate {
func loadNewScreen(controller: UIViewController) {
self.present(controller, animated: true, completion: nil)
}
}
最终源代码 可在此处找到:https://github.com/fossasia/susi_iOS/pull/372