iosswiftavplayerlayer

Centering a CALayer Horizontally Inside UIView


As the title says, I've gotten my video player to work, but it shows the video on the right side of the cell instead of centered. Not sure why, I've tried a lot of things on the internet, and I know my code is probably not amazing, but I just want to center it horizontally and it refuses to work with me. If I turn off playerView.translatesAutoresizingMaskIntoConstraints = false it just outright disappears

img_thumb is an image that takes up the entirety of the space that will be a video thumbnail before it loads but right now is just a black background for the video, either way the full contentView has an additional element at the bottom so it's easier to use the img_thumb constraints

    playerView.frame = videoCell.img_thumb.frame
//                            playerView.layer.bounds = videoCell.img_thumb.bounds
//                            playerView.layer.frame = videoCell.img_thumb.frame
    playerView.layer.position = CGPoint(x: videoCell.img_thumb.bounds.midX, y: videoCell.img_thumb.bounds.midY)
    videoCell.contentView.addSubview(playerView)
    playerView.translatesAutoresizingMaskIntoConstraints = false
    playerView.bounds = videoCell.img_thumb.bounds
    playerView.center = videoCell.img_thumb.center
    playerView.centerXAnchor.constraint(equalTo: videoCell.img_thumb.centerXAnchor).isActive = true
    playerView.centerYAnchor.constraint(equalTo: videoCell.img_thumb.centerYAnchor).isActive = true
    playerView.heightAnchor.constraint(lessThanOrEqualTo: videoCell.img_thumb.heightAnchor).isActive = true
    playerView.widthAnchor.constraint(lessThanOrEqualTo: videoCell.img_thumb.widthAnchor).isActive = true

My layout in Storyboard Layout in Storyboard

The reason I use the UIImageView's constraints Reason I use UIImageView's constraints (orange bar)

The constraints for ContentView of HomeVideoCell constraints for ContentView

The actual created PlayerView created PlayerView view

And finally what it actually looks like (with AutoLayout on) What it looks like with autolayout

and finally PlayerView

import Foundation
import UIKit
import AVKit

class PlayerView: UIView {
    private var url: URL?
    private var urlAsset: AVURLAsset?
    private var playerItem: AVPlayerItem?
    var loaded: Bool = false
    var activityIndicator: UIActivityIndicatorView?
    
    private var assetPlayer:AVPlayer? {
        didSet {
            DispatchQueue.main.async {
                if let layer = self.layer as? AVPlayerLayer {
                    layer.player = self.assetPlayer
                }
            }
        }
    }
    
    override class var layerClass: AnyClass {
        return AVPlayerLayer.self
    }
    
    init() {
        super.init(frame: .zero)
        initialSetup()
    }
    
    required init?(coder: NSCoder) {
        super.init(frame: .zero)
        initialSetup()
    }
    
    private func initialSetup() {
        if let layer = self.layer as? AVPlayerLayer {
            // Do any configuration
            layer.videoGravity = AVLayerVideoGravity.resizeAspect
        }
    }
    
    func prepareToPlay(withUrl url:URL, shouldPlayImmediately: Bool = false) {
        guard !(self.url == url && assetPlayer != nil && assetPlayer?.error == nil) else {
            if shouldPlayImmediately {
                play()
            }
            return
        }
        
        cleanUp()
        
        self.url = url
        
        let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
        let urlAsset = AVURLAsset(url: url, options: options)
        self.urlAsset = urlAsset
        
        let keys = ["tracks"]
        urlAsset.loadValuesAsynchronously(forKeys: keys, completionHandler: { [weak self] in
            guard let strongSelf = self else { return }
            strongSelf.startLoading(urlAsset, shouldPlayImmediately)
        })
        NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
    }
    
    private func startLoading(_ asset: AVURLAsset, _ shouldPlayImmediately: Bool = false) {
        var error:NSError?
        let status: AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)
        if status == AVKeyValueStatus.loaded {
            let item = AVPlayerItem(asset: asset)
            self.playerItem = item
            
            let player = AVPlayer(playerItem: item)
            self.assetPlayer = player
            self.loaded = true
            
            if shouldPlayImmediately {
                DispatchQueue.main.async {
                    player.play()
                }
            }
        }
    }
    
    func toggle() {
        if self.assetPlayer?.isPlaying == false {
            play()
        } else {
            pause()
        }
    }
    
    func play() {
        guard self.assetPlayer?.isPlaying == false else { return }
//        if self.loaded && self.activityIndicator != nil {
//            self.activityIndicator?.stopAnimating()
//        }
        DispatchQueue.main.async {
            self.assetPlayer?.play()
        }
    }
    
    func pause() {
        guard self.assetPlayer?.isPlaying == true else { return }
        DispatchQueue.main.async {
            self.assetPlayer?.pause()
        }
    }
    
    func cleanUp() {
        pause()
        urlAsset?.cancelLoading()
        urlAsset = nil
        assetPlayer = nil
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
    }
    
    deinit {
        cleanUp()
    }
    
    @objc private func playerItemDidReachEnd(_ notification: Notification) {
        guard notification.object as? AVPlayerItem == self.playerItem else { return }
        DispatchQueue.main.async {
            guard let videoPlayer = self.assetPlayer else { return }
            videoPlayer.seek(to: .zero)
            videoPlayer.play()
        }
    }
}

Solution

  • I as mentioned in comments it's really hard to figure out your problem. But try to replace the given code with this.Delete the block of code you shown me up, then replace it.

        videoCell.contentView.addSubview(playerView)
        playerView.translatesAutoresizingMaskIntoConstraints = false
        playerView.centerXAnchor.constraint(equalTo: videoCell.img_thumb.centerXAnchor).isActive = true
        playerView.centerYAnchor.constraint(equalTo: videoCell.img_thumb.centerYAnchor).isActive = true
        playerView.heightAnchor.constraint(lessThanOrEqualTo: videoCell.img_thumb.heightAnchor).isActive = true
        playerView.widthAnchor.constraint(lessThanOrEqualTo: videoCell.img_thumb.widthAnchor).isActive = true