Reputation: 143
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
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
Reputation: 3654
You need to tell autolayout how the playerView height will be calculated. You can try the following options:
set a constant height to playerView
playerView.heightAnchor.constraint(equalToConstant: 100)
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