#ios #swift
#iOS #быстрый
Вопрос:
Я пытаюсь удалить AVPlayerLayer, чтобы избежать потенциальных проблем с памятью, но он выходит из строя.
У меня есть основной контроллер представления коллекции, который отображает TweetCells
, и в каждом TweetCell
из них у меня есть mediaCollectionView
отображение горизонтальной прокрутки MediaCells
. Каждая из этих медиасетей будет содержать AVPlayer, если отображаемый контент представляет собой видео.
Как я понял из нескольких сообщений SO, удаление AVPlayerLayer является правильной практикой, и поэтому я внедряю его в prepareForReuse
.
Вот моя реализация:
//At TweetCell
class TweetCell: UICollectionViewCell {
lazy var mediaCollectionView: UICollectionView = {
let size = NSCollectionLayoutSize(
widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
heightDimension: NSCollectionLayoutDimension.fractionalHeight(1)
)
let item = NSCollectionLayoutItem(layoutSize: size)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitem: item, count: 1)
let section = NSCollectionLayoutSection(group: group)
section.orthogonalScrollingBehavior = .paging
let layout = UICollectionViewCompositionalLayout(section: section)
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .systemGray
cv.translatesAutoresizingMaskIntoConstraints = false
cv.dataSource = self
cv.delegate = self
cv.register(MediaCell.self, forCellWithReuseIdentifier: "cell")
return cv
}()
var tweet: Tweet? {
didSet {
if let tweet = tweet {
//Setup other UI elements...
//Refresh mediaCollectionView
mediaCollectionView.reloadData()
mediaCollectionView.scrollToItem(at: IndexPath(item: 0, section: 0), at: .top, animated: false)
}
}
}
override func prepareForReuse() {
tweet = nil
//Other UI elements reset, eg, profileImageView.image = nil, or tweetLabel.text = ""
super.prepareForReuse()
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MediaCell
if tweet?.photosArray != nil {
cell.media = tweet?.photosArray?[indexPath.item]
} else if let videos = tweet?.videosArray {
cell.media = videos[indexPath.item]
}
return cell
}
//At MediaCell
class MediaCell: UICollectionViewCell {
let imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
iv.contentMode = .scaleAspectFill
iv.clipsToBounds = true
iv.backgroundColor = .systemGreen
return iv
}()
var videoView: AVPlayerView = {
let v = AVPlayerView()
v.translatesAutoresizingMaskIntoConstraints = false
v.isHidden = false
v.backgroundColor = .systemBlue
return v
}()
var player : AVPlayer?
var playerLayer: AVPlayerLayer? //Not in use
var media: [String: AnyObject]? {
didSet {
guard let media = media else {return}
if mediaType == "photo" {
//PHOTOS
///Toggle image/video view
removeAndDeactivateSubview(view: videoView)
addAndActivateSubview(view: imageView)
let url = URL(string: media["media_url_https"] as? String ?? "")
imageView.sd_setImage(with: url)
} else if mediaType == "video" {
//VIDEOS
///Toggle image/video view
removeAndDeactivateSubview(view: imageView)
addAndActivateSubview(view: videoView)
guard let urlString = media["url"] as? String else {return}
guard let url = URL(string: urlString) else {return}
player = AVPlayer(url: url)
player?.isMuted = true
let castedLayer = videoView.layer as? AVPlayerLayer
castedLayer?.player = player
} else {
print("Some other media content type")
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
func addAndActivateSubview(view: UIView) {
addSubview(view)
activateSubview(view: view)
}
func removeAndDeactivateSubview(view: UIView) {
deactivateSubview(view: view)
view.removeFromSuperview()
}
func activateSubview(view: UIView) {
view.topAnchor.constraint(equalTo: topAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
}
func deactivateSubview(view: UIView) {
view.topAnchor.constraint(equalTo: topAnchor).isActive = false
view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = false
view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = false
view.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = false
}
override func prepareForReuse() {
imageView.image = nil
player = nil
//CRASH HERE
if let layer = videoView.layer as? AVPlayerLayer {
layer.player = nil
layer.removeFromSuperlayer()
}
super.prepareForReuse()
}
//Custome AVPlayerView
class AVPlayerView: UIView {
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
}
Сообщение о сбое указывает на .layer
объект в if let layer = videoView.layer as? AVPlayerLayer
со следующим сообщением:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x47cd649dd8e0)
После этого я попытался удалить AVPlayerLayer super.prepareForReuse
, но он также выходит из строя.
Комментарии:
1. Вы пытались поместить этот код в tableViewDelegate->DidEndDisplatingCell вместо этого? Это может иметь значение
2. @ArikSegal Да, я пытался это сделать. Я поместил его в didEndDisplaying HomeController, и он разбился
super.prepareForReuse()
приTweetCell
входе.