今天在工作中遇到了需要让 AVPlayerLayer
支持 autolayout
的问题。因为我播放器的 playerView
是通过约束决定大小的,目的为了适应小屏手机。我需要让我的 AVPlayerLayer
充满我的 playerView
,如果有 autolayout
这个问题就非常好解决,但是 layer
是不支持 autolayout
的,我想到了两种解决方式。
方法一:
自定义 PlayerView
继承自 UIView
,然后重写 layoutSubviews
方法,让 layoutSubviews
去执行一个回调 layoutCallback
,我们在这个回调中调整 playerLayer
的 frame
。
代码大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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
对象。
最后代码大致如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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 } }
|
问题完美解决。