Search code examples
iosswiftuiimageviewavplayeravplayerlayer

AVPlayerLayer not sizing correctly


I have a custom UITableViewCell that displays either an image or a video. If there's a video, it initially downloads and displays the thumbnail from that video while the video is loading and preparing to play.

It all works, the problem is that I get the ratio of the thumbnail, store it in the database and when I download the thumbnail, it makes the height based on that ratio. It works great for images. But the issue is that the AVPlayerLayer frame doesn't cover the entire thumbnail and it looks like this:

enter image description here

Here's how I do it:

 func updateView() {

    if let videoUrlString = post?.videoUrl, let videoUrl = URL(string: videoUrlString) {
        volumeView.isHidden = false
        player = AVPlayer(url: videoUrl)
        playerLayer = AVPlayerLayer(player: player)
        playerLayer!.frame = postImageView.frame // thumbnail image
        playerLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill
        contentView.layer.addSublayer(playerLayer!)
        self.volumeView.layer.zPosition = 1
        layoutIfNeeded()
        player?.play()
        player?.isMuted = videoIsMuted
    }

    if let ratio = post?.ratio {
        photoHeightConstraint.constant = UIScreen.main.bounds.width / ratio
        layoutIfNeeded()
    }
 }

I'm not doing something right apparently or I'm missing something based on my final result. Any clue?

UPDATE: I notice that if you scroll the cells slowly, the video is being displayed according to the size of the thumbnail. But if you scroll fast, it mismatches the frame. I assume it has something to do with reusability or it simply cannot catch up with the frame? Here's my code for prepareForReuse() method:

    override func prepareForReuse() {
    super.prepareForReuse()
    profileImageView.image = UIImage(named: "placeholder")
    postImageView.image = UIImage(named: "profile_placeholder")
    volumeView.isHidden = true
    if let p = player, let pLayer = playerLayer {
        p.pause()
        pLayer.removeFromSuperlayer()
    }
}

Solution

  • The solution turned out to be rather simple. Basically each post contains either post or a video, but either way, it always contains an image. I also set the ratio of that image (the width devided by the height). So, all I had to do is set the player's width to the screen width devided by the ratio and it gives your the proper frame.

    playerLayer.frame.size.height = UIScreen.main.bounds.width / postRatio