The view.layer.presentation() 's value is not accurate.

I add two view on screen. One is the blue view, other is orange view.

And I use CADisplayLink to change orange view 's frame according to the blueView.layer.presentation.frame

As expected, the orange view should cover the blue view completely, but we can see a gap and we can see a little part of blue view during keyboard animation. Here is demo code:


class SecondViewController: UIViewController {
   
  let textField = UITextField()
   
  override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.textField.resignFirstResponder()
  }
   
  lazy var displayLink = CADisplayLink(target: self, selector: #selector(self.onDisplayLink))
   
  @objc private func onDisplayLink () {
    self.realBox.frame = CGRect(x: self.realBox.frame.origin.x, y: self.blueBox.layer.presentation()!.frame.origin.y, width: self.view.bounds.width, height: 100)
  }
   
  let blueBox = UIView()
  let realBox = UIView()
   
  override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = .white
     
    textField.frame = .init(x: 100, y: 100, width: 100, height: 100)
    textField.backgroundColor = .red
    self.view.addSubview(textField)
     
    realBox.backgroundColor = .orange
     
    blueBox.backgroundColor = .blue
    blueBox.frame = .init(x: 0, y: self.view.bounds.height - 100, width: self.view.bounds.width, height: 100)
    self.view.addSubview(blueBox)
    self.view.addSubview(realBox)
    realBox.frame = .init(x: 0, y:self.view.bounds.height - 100 , width: self.view.bounds.width, height: 100)
     
    NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main) { noti in
      let userInfo = noti.userInfo!
      let endFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! CGRect
      let isOpen = endFrame.intersects(self.view.bounds)
      self.blueBox.frame = .init(x: 0, y: isOpen ? self.view.bounds.height - 100 - endFrame.height : self.view.bounds.height - 100, width: self.view.bounds.width, height: 100)
    }
     
    if #available(iOS 15.0, *) {
      self.displayLink.preferredFrameRateRange = .init(minimum: 60, maximum: 120, preferred: 120)
    } else {
      self.displayLink.preferredFramesPerSecond = 120
    }
    self.displayLink.add(to: .main, forMode: .common)
  }
}

So how to get an accurate value of the layer currently displayed on screen?

Here is the video: https://github.com/luckysmg/daily_images/blob/main/RPReplay_Final1661168764.mov?raw=true

Could you show the result you get ?

Hi, I have add the video link in post😄

This is an expected result when you mix animation methods.

blueBox is being animated via a standard CAAnimation (via UIKit's animation APIs) and so is being animated in the window server. realBox is being animated via a CADisplayLink and thus is being animated in the application process.

A layer's presentationLayer is an estimate of the properties of the layer as displayed in the window server, but since time is still passing in both the window server and the app, it is usually not 100% accurate for any properties that are actually animating. Further the CADisplayLink is preparing the next frame. As such realBox is constantly in a state of trying to catch up to where blueBox was.

Minimally, you need to determine where realBox will be when the current frame is committed in order to avoid the issue you are seeing, but realistically thats going to take a fair amount of code and still may not give satisfactory results. A better solution would be to ensure both views are animated with the same animation system (and at the same time, that is set both frames inside the keyboard notification).

The view.layer.presentation() 's value is not accurate.
 
 
Q