Search code examples
objective-cswiftmacosautolayoutnswindow

How do I animate a NSWindow's size change when it gets resized by Auto Layout?


Consider an NSWindow whose height is controlled by a set of complex constraints, and I want to have the window's resizing animated whenever the constraints cause the window to get resized.

As a simply example, let's just assume that the Window has no subviews, and instead of a set of complex constraints we just use a single constraint that directly controls the window's content view's height.

Now, when I change the constraint, the window gets implicitly resized. But since I do not set the window's frame myself (in the complex model I can't predict the new size!), I cannot use the animated window resize function that's commonly used for animating window resizes. So, how do I get the resize animated when Auto Layout has control over the resizing?

Here's an example in Swift. To use it, simply create a new plain "Cocoa App" project in Xcode and then replace the following function in AppDelegate:

func applicationDidFinishLaunching(_ aNotification: Notification) {
    let view = self.window.contentView!

    // Add a height constraint to the window's view
    let heightConstraint = NSLayoutConstraint.init(item: view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: view.frame.size.height)
    view.addConstraint(heightConstraint)

    // Resize the window after a brief delay
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {

        // Now change the constraint, which results in the window getting resized instantly.
        // But I would like the window's size change animated. How do I accomplish this?
        NSAnimationContext.runAnimationGroup(
            { context in
                context.duration = 0.5
                heightConstraint.constant = heightConstraint.constant + 100
            },
            completionHandler:nil
        )

    }
}

Solution

  • there is a proxy object for constraints. instead of changing the constraint's constant, set animator()'s constant. It will animate. After animation end's call self.layoutSubtreeIfNeeded() to update the constraints.

    NSAnimationContext.runAnimationGroup({ context in
        context.duration = 0.5
        context.allowsImplicitAnimation = true
        heightConstraint.animator().constant = heightConstraint.constant + 100
    }, completionHandler: {
        view.layoutSubtreeIfNeeded()
    })