Yoomama
Yoomama

Reputation: 143

AVPlayer inside UITableViewCell with other Views auto layout

I have a UITableViewCell that has a UILabel and an AVPlayer that plays videos from online that I need to implement. The problem is that AVPlayer that Apple gives cannot use auto layout to correctly show the video.

I believe there are methods for implementing a UIView for a mixed constraint and frame layout. I think there is more to this question though: there seems to be an issue with the fact that the video does not load on init.

UITableViewCell

import UIKit
import AVKit
import AVFoundation

class PlayerView: UIView {
    var player: AVPlayer? {
        get {
            return playerLayer.player
        }
        set {
            playerLayer.videoGravity = .resizeAspect
            playerLayer.player = newValue
        }
    }

    var playerLayer: AVPlayerLayer {
        return layer as! AVPlayerLayer
    }

    override static var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
}

class CustomTableCell: UITableViewCell {
    let titleView = UILabel()
    let containerView = UIView()
    let playerView = PlayerView()
    required init?(coder aDecoder: NSCoder) {super.init(coder: aDecoder)}
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        titleView.text = "Title"
        titleView.translatesAutoresizingMaskIntoConstraints = false

        contentView.clipsToBounds = true
        containerView.clipsToBounds = true
        contentView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        //contentView.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
        var lg = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: lg.topAnchor),
            containerView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
            containerView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
            containerView.bottomAnchor.constraint(equalTo: lg.bottomAnchor)
        ])

        containerView.addSubview(titleView)
        containerView.layoutMargins = UIEdgeInsets(top: 15, left: 17, bottom: 15, right: 17)
        lg = containerView.layoutMarginsGuide

        playerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(playerView)
        NSLayoutConstraint.activate([
            titleView.topAnchor.constraint(equalTo: lg.topAnchor),
            titleView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
            titleView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
//            titleView.bottomAnchor.constraint(equalTo: lg.bottomAnchor),

            playerView.topAnchor.constraint(equalTo: titleView.bottomAnchor),
            playerView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
            playerView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
            playerView.bottomAnchor.constraint(equalTo: lg.bottomAnchor)
        ])
    }
}

I've setup a UITableView in my view controller, but the main point is the UITableViewDataSource cellForRowAt:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReuseableCell(withIdentifier: "cell") as! CustomTableCell
    let url = NSURL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
    let avPlayer = AVPlayer(url: url! as URL)
    cell.playerView.playerLayer.player = avPlayer
    return cell
}

But I don't see the video. Obviously, there is a function like willLayoutSubviews or something along those lines, but how would I break the constraint of the title view to constrain with the AVPlayerLayer or something like that so I CAN see the video?

In general, how would I see that AVPlayer or any player inside of a custom UITableViewCell that has other auto layout views?


Edit 1: The answer @ugur gave is correct since the AVPlayer needs to resize. Expanding the view using leadngAnchor and trailingAnchor cannot solve the problem since that only expanded was the size of the current view was.

Using a centerXAnchor, widthAnchor, and heightAnchor will work. A centerXAnchor will center your player in the view.

Upvotes: 5

Views: 1965

Answers (2)

Harshal Bhavsar
Harshal Bhavsar

Reputation: 1673

So your main problem here is you are not able to see your player inside the cell. I have modified some of you code and then it's showing and playing the video also.

NOTE: It's very basic code and I am sure you will be able to improve it further as per your requirements.

Your PlayerView stays same what changes is as follows. I have added new method configureFor which will only get the url as parameter and the setup the player.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableCell
    let url = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
    cell.configureFor(url!)
    return cell
}

Following is your code for the cell. Here i have moved the UI setup code to a common function commonInit() and then made use of it as follow.

    class CustomTableCell: UITableViewCell {

    let titleView = UILabel()
    let containerView = UIView()
    let playerView = PlayerView()

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        commonInit()
    }

    func commonInit() {
        titleView.text = "Title"
        titleView.translatesAutoresizingMaskIntoConstraints = false

        contentView.clipsToBounds = true
        containerView.clipsToBounds = true
        containerView.backgroundColor = .orange
        contentView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        //contentView.layoutMargins = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
        var lg = contentView.layoutMarginsGuide
        NSLayoutConstraint.activate([
            containerView.topAnchor.constraint(equalTo: lg.topAnchor),
            containerView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
            containerView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
            containerView.bottomAnchor.constraint(equalTo: lg.bottomAnchor)
        ])

        containerView.addSubview(titleView)
        containerView.layoutMargins = UIEdgeInsets(top: 15, left: 17, bottom: 15, right: 17)
        lg = containerView.layoutMarginsGuide

        playerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.addSubview(playerView)
        NSLayoutConstraint.activate([
            titleView.topAnchor.constraint(equalTo: lg.topAnchor),
            titleView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
            titleView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
            //            titleView.bottomAnchor.constraint(equalTo: lg.bottomAnchor),

            playerView.topAnchor.constraint(equalTo: titleView.bottomAnchor),
            playerView.leadingAnchor.constraint(equalTo: lg.leadingAnchor),
            playerView.trailingAnchor.constraint(equalTo: lg.trailingAnchor),
            playerView.bottomAnchor.constraint(equalTo: lg.bottomAnchor)
        ])
    }

    func configureFor(_ url: URL) {
        let avPlayer = AVPlayer(url: url)
        self.playerView.playerLayer.player = avPlayer
        self.playerView.playerLayer.player?.play()
    }
}

Upvotes: 0

ugur
ugur

Reputation: 3654

You need to tell autolayout how the playerView height will be calculated. You can try the following options:

  1. set a constant height to playerView

    playerView.heightAnchor.constraint(equalToConstant: 100)
    
  2. or set a aspect ratio to playerView

    playerView.heightAnchor.constraint(equalTo: playerView.widthAnchor, multiplier: 1.0)
    

So autolayout will know how to position your playerView. otherwise playerView height will be 0.

Recommendations:

Don't create AVPlayer in cellFor instead do it in cell init or awakeFromNib (if storyboard is used). So AVPlayer will be reusable too. Just reset the setup with new url or playerItem. You can also use play or pause depending on ur case. But just don't recreate the AVPlayer every time cellFor is called.

Upvotes: 1

Related Questions