2

I found different posts where people have problems to show CATextLayer like this or that.

The solution from the threads unfortunately only partially work for me. My problem is that my CATextLayer sometimes show text and sometimes not.

The structure: I have an AVSynchronizedLayer, on this I add a CATextLayer.

For illustration the AVSynchronizedLayer has a yellow background. The CATextLayer has a red background with font color cyan.

let synchronizedLayer = AVSynchronizedLayer(playerItem: playerItem)
synchronizedLayer.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
synchronizedLayer.opacity = 1.0
synchronizedLayer.beginTime = 0.5
synchronizedLayer.backgroundColor = UIColor.yellow.cgColor

The text layer:

        let textlayer = CATextLayer()
        textlayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        textlayer.fontSize = 20
        let myAttributes = [
            NSAttributedString.Key.font: UIFont(name: "Chalkduster", size: 30.0)! , 
            NSAttributedString.Key.foregroundColor: UIColor.cyan,                   
            NSAttributedString.Key.backgroundColor: UIColor.red
        ]
        let myAttributedString = NSAttributedString(string: "My text", attributes: myAttributes )

        textlayer.alignmentMode = .center
        textlayer.string = myAttributedString
        textlayer.isWrapped = true
        textlayer.beginTime = 1.0
        textlayer.opacity = 1.0
        textlayer.truncationMode = .end

What I am doing with the layers

// playerItem is a bundle of 5 short movies (3 seconds long). Between every video is a opacity transition
let player = AVPlayer(playerItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)
// [...] config for playerLayer

 synchronizedLayer.addSublayer(textlayer)
 playerLayer.addSublayer(synchronizedLayer)        
 
return playerLayer // to the view   

I use SwiftUI (min version iOS 15). But SwiftUI does not offer the possibility to use an 'AVPlayer' directly. I had to build a kind of bridge.

struct CustomPlayerView: UIViewRepresentable {
    
    let playerLayer: AVPlayerLayer
    func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<CustomPlayerView>) {
    }
    
    func makeUIView(context: Context) -> UIView {
        let customPlayerUIView = CustomPlayerUIView(frame: .zero)
        customPlayerUIView.initPlayer(playerLayer: playerLayer)
        return customPlayerUIView
    }
}

The initPlayer does the following thing

class CustomPlayerUIView: UIView {
    private var playerLayer: AVPlayerLayer?
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    func initPlayer(playerLayer: AVPlayerLayer) {
        self.playerLayer = playerLayer
        layer.addSublayer(playerLayer)
        playerLayer.player?.play()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
    }
}

What always appears is a yellow box (AVSynchronizedLayer) and a red box (CATextLayer). Only the text is always displayed randomly.

I am quite sure it's a lifecycle or rendering problem. But I don't know exactly what the correct lifecycle is. I have almost everywhere already layoutIfNeeded() or displayIfNeeded() called. But this had no effect.

Result 1 and Result 2 are produced with the same code.

kuzdu
  • 7,124
  • 1
  • 51
  • 69
  • What if you change `textlayer.beginTime = 1.0` to zero, or just comment out the line? – matt Aug 27 '22 at 12:37
  • Also please note that the red is not the background color of the text layer. It's the background color of the text layer's text. The text layer has no background color (`backgroundColor`, a CGColor); you might wish to give it one. – matt Aug 27 '22 at 12:40
  • 1
    `textlayer.beginTime = 1.0` changes has no effects. When I set a `backgroundColor` this one is ignored (or not visible). When I remove the `NSAttributedString.Key.backgroundColor` the `backgroudColor` of the layer will be visible. When I don't add any backgroundColor then there appear nothing and sometimes just the text – kuzdu Aug 27 '22 at 15:19
  • Okay, that's useful. Unfortunately the dribs and drabs of code you've provided are not sufficient to let me try this out for myself. – matt Aug 27 '22 at 16:11
  • I can create a public git with in example project. Then you can do the best.I would be more than grateful! Should I create one? – kuzdu Aug 27 '22 at 16:13
  • We can certainly give it a try! – matt Aug 27 '22 at 16:56
  • So I created the issue: Check this repo: https://github.com/kuzdu/VideoIssueCaLayer/tree/master If you have any trouble to check it out or to compile (I don't think so), please let me know. The code in the repository should output a text to me from second 0. This is not the case. Sometimes the text appears after a few seconds, sometimes not. Good luck and so much thanks for your efforts! – kuzdu Aug 27 '22 at 19:08

1 Answers1

0

The main problem seems to be this line:

    playerLayer.player?.play()

You're apparently saying that before the player layer is ready to play. (Actually, you're saying it before the player layer is even in the window.) I was able to get the text to appear reliably by waiting an arbitrarily long time before trying to play:

func initPlayer(playerLayer: AVPlayerLayer) {
    self.playerLayer = playerLayer
    layer.addSublayer(playerLayer)
    Task { @MainActor in
        try? await Task.sleep(nanoseconds: 5_000_000_000)
        self.playerLayer?.player?.play()
    }
}

I'm afraid I'm a bit hazy on what we're actually waiting for but clearly we can't play until it (whatever it is) has happened. You know your code better than I do, so maybe you can work out what it is.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks matt for your effort! Your fix is working for the example. Thanks for that. For my production code I have to increase the time to minimum 10 seconds. I found a better solution and I post it asap. So I don't vote your answer up. I hope this is okay. But I really like your engagement. Thanks! – kuzdu Aug 29 '22 at 11:47
  • No problem! The "answer" was just a way to provide my observations with formatting. :) – matt Aug 29 '22 at 13:02