Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animating Auto Layout constraints with NSView.layoutSubtreeIfNeeded() not working on macOS High Sierra

I have a basic Mac app with a view animation done through Auto Layout:

  • I add a new view to the right of the current view
  • I update the constraints so that the new view ends up filling the window

→ The animation will make it appear as if the view slides in from the right.

The recommended way for animating Auto Layout changes is:

  1. Update the constraints
  2. Use NSAnimationContext.runAnimationGroup()
  3. Set allowsImplicitAnimation to true inside the animation block
  4. Call view.layoutSubtreeIfNeeded() inside the animation block

I followed this recommendation and everything worked fine on macOS Sierra, but on macOS High Sierra, the animation does not take place anymore. Instead the view shows up at its final position without the animation.

I found a workaround: I schedule the animation on the next runloop cycle using DispatchQueue.main.async. However, that seems like a hack and I'm wondering if there is something else I'm missing here.

Here is my actual code:

private func appendSlideViewControllerAnimated(_ viewController:NSViewController, to viewToTheLeft:NSView)
{
    // Insert the new view on the very right, just outside the parent:

    viewController.view.frame = self.view.bounds
    viewController.view.translatesAutoresizingMaskIntoConstraints = false

    view.addSubview(viewController.view)

    viewController.view.topAnchor.constraint(     equalTo: view.topAnchor     ).isActive = true
    viewController.view.bottomAnchor.constraint(  equalTo: view.bottomAnchor  ).isActive = true

    viewController.view.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true

    viewController.view.leadingAnchor.constraint(equalTo: viewToTheLeft.trailingAnchor).isActive = true

    // Update the layout after we just added the view on the right: 
    view.layoutSubtreeIfNeeded()

    // Starting with macOS High Sierra, animating constraint changes for the newly inserted view
    // only works if scheduled on the next runloop:
    //DispatchQueue.main.async {


         // Update the constraints to pin the view to the left:

        self.view.removeConstraint(self.activeSlideLeadingConstraint!)

        self.activeSlideLeadingConstraint = viewController.view.leadingAnchor.constraint(equalTo: self.view.leadingAnchor)
        self.activeSlideLeadingConstraint?.constant = 0
        self.activeSlideLeadingConstraint?.isActive = true

        NSAnimationContext.runAnimationGroup( { context in

            self.isAnimating = true

            context.duration = self.slidingAnimationDuration
            context.allowsImplicitAnimation = true


            self.view.layoutSubtreeIfNeeded()

        }, completionHandler: {

            viewToTheLeft.removeFromSuperview()

            self.clearUndoHistory()

            self.updateFirstResponder()

            self.isAnimating = false
        })
    //}
}
like image 260
Mark Avatar asked Nov 14 '17 10:11

Mark


1 Answers

Enable Core Animation backing for root view you trying to animate. It can be done in Interface Builder or programmatically:

override func viewDidLoad()
{
    super.viewDidLoad()
    view.wantsLayer = true
}
like image 183
Andriy Avatar answered Oct 31 '22 11:10

Andriy