今天在工作中遇到了需要让 AVPlayerLayer 支持 autolayout 的问题。因为我播放器的 playerView 是通过约束决定大小的,目的为了适应小屏手机。我需要让我的 AVPlayerLayer 充满我的 playerView,如果有 autolayout 这个问题就非常好解决,但是 layer 是不支持 autolayout 的,我想到了两种解决方式。
方法一:
自定义 PlayerView 继承自 UIView,然后重写 layoutSubviews 方法,让 layoutSubviews 去执行一个回调 layoutCallback,我们在这个回调中调整 playerLayer 的 frame。
代码大致如下:
class PlayerView: UIView {
var layoutCallback: ((UIView) -> Void)?
override func layoutSubviews() {
layoutCallback?(self)
}
}
class DemoViewController: UIViewController {
@IBOutlet private(set) weak var playerView: PlayerView!
private var playerLayer: AVPlayerLayer!
override func viewDidLoad() {
super.viewDidLoad()
let player = AVPlayer(url: URL(string: "")!)
playerLayer = AVPlayerLayer(player: player)
playerView.layoutCallback = { [weak self] in
self?.playerLayer.frame = $0.frame
}
}
}
这样虽然能够解决问题,但是在加载的时候会明显看到 playerLayer 的 frame 被调整。这自然不是我们想要的。
方法二:
我们将 UIView 的 layer 直接替换成 AVPlayerLayer 然后就不需要设置 layer 的 frame 它会自动充满整个 View。这样我们完全不需要去初始化一个新的 AVPlayerLayer,用起来也方便不少。
想要替换 View 的 layer 我们需要重写 layerClass 属性。官方对 layerClass 的解释如下:
这个方法默认返回
CALayer类对象。子类可以覆盖此方法,并根据需要返回不同的layer class。例如,如果你的视图使用平铺显示一个大的可滚动区域,您可能希望覆盖此属性并返回CATiledLayer类。在创建视图的早期只调用此方法一次,以便创建相应的
layer对象。
最后代码大致如下:
class PlayerView: UIView {
override class var layerClass: AnyClass {
return AVPlayerLayer.self
}
var playerLayer: AVPlayerLayer? {
return self.layer as? AVPlayerLayer
}
}
class DemoViewController: UIViewController {
@IBOutlet private(set) weak var playerView: PlayerView!
override func viewDidLoad() {
super.viewDidLoad()
playerView.playerLayer?.player = AVPlayer(url: URL(string: "")!)
playerView.playerLayer?.videoGravity = .resizeAspectFill
}
}
问题完美解决。