Search code examples
objective-cswiftmacoscocoaappkit

How to pin NSViewController to top of NSPopover during NSViewController.transition?


I've been trying to create a sliding transition from one child view controller to another inside an NSPopover.

My problem is that the child view controllers do not stick to the top of the NSPopover during the transition. They animate in from the bottom or top:

Demo GIF

Expected behaviour: both child view controllers should stick to the top during the transition and should simply slide over horizontally.

This is the function I wrote to trigger the transition:

func loadViewController(_ childViewController: NSViewController, withTransition transitionOptions: NSViewController.TransitionOptions?) {
        addChild(childViewController)
        view.addSubview(childViewController.view)

        childViewController.view.layer?.borderColor = NSColor(calibratedRed: 0, green: 255, blue: 0, alpha: 1).cgColor
        childViewController.view.layer?.borderWidth = 2

        childViewController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        view.layout()

        let oldViewController = currentViewController
        currentViewController = childViewController

        oldViewController?.view.layer?.borderColor = NSColor(calibratedRed: 255, green: 0, blue: 0, alpha: 1).cgColor
        oldViewController?.view.layer?.borderWidth = 2

        if let oldViewController = oldViewController  {
            transition(from: oldViewController, to: currentViewController!, options: transitionOptions ?? .slideLeft, completionHandler: { [weak oldViewController] in
                oldViewController?.removeFromParent()
                oldViewController?.view.removeFromSuperview()
            })
        }

        NSAnimationContext.runAnimationGroup({ (context) -> Void in
            context.duration = 0.5
            context.allowsImplicitAnimation = true

            self.parentPopover?.contentSize = NSSize(width: childViewController.preferredContentSize.width, height: childViewController.preferredContentSize.height)
        })
    }

Any idea what could be causing the issue? I've tried playing around with the constraints of both the child and parent view controllers as well as their frame sizes. I just can't figure out what I'm doing wrong.

I've uploaded the complete reproducible example here: https://github.com/maximilianschmitt/DebugPopoverAnimation

Thanks a lot for your help!


Solution

  • If you expect as on below animation

    demo

    then just remove update of content size from animation block, as below

    self.parentPopover?.contentSize = NSSize(width: childViewController.preferredContentSize.width, height: childViewController.preferredContentSize.height)
    NSAnimationContext.runAnimationGroup({ (context) -> Void in
        context.duration = 0.5
        context.allowsImplicitAnimation = true
    
    }) {
        oldViewController?.removeFromParent()
        oldViewController?.view.removeFromSuperview()
    }
    

    Update: keep popover content animatable (above changes are not needed)

    demo2

    For this case the only you need is to flip coordinate system for popover content view (which is a view of MasterViewController)

    class PopoverContentView: NSView {
        override var isFlipped: Bool { true }
    }
    
    class MasterViewController: NSViewController {
        ...
        override func loadView() {
            self.view = PopoverContentView()
        }