layerClass

今天在工作中遇到了需要让 AVPlayerLayer 支持 autolayout 的问题。因为我播放器的 playerView 是通过约束决定大小的,目的为了适应小屏手机。我需要让我的 AVPlayerLayer 充满我的 playerView,如果有 autolayout 这个问题就非常好解决,但是 layer 是不支持 autolayout 的,我想到了两种解决方式。

方法一:

自定义 PlayerView 继承自 UIView,然后重写 layoutSubviews 方法,让 layoutSubviews 去执行一个回调 layoutCallback,我们在这个回调中调整 playerLayerframe

代码大致如下:

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
}
}
}

这样虽然能够解决问题,但是在加载的时候会明显看到 playerLayerframe 被调整。这自然不是我们想要的。

方法二:

我们将 UIViewlayer 直接替换成 AVPlayerLayer 然后就不需要设置 layerframe 它会自动充满整个 View。这样我们完全不需要去初始化一个新的 AVPlayerLayer,用起来也方便不少。

想要替换 Viewlayer 我们需要重写 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
}
}

问题完美解决。