今天在工作中遇到了需要让 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 } }
|
问题完美解决。