Search code examples
multithreadingcore-animationswiftnssplitview

NSSplitViewItem collapse animation and window setFrame conflicting


I am trying to make a (new in 10.10) NSSplitViewItem collapse and uncollapse whilst moving its containing window so as to keep the whole thing "in place".

The problem is that I am getting a twitch in the animation (as seen here).

The code where I'm doing the collapsing is this:

func togglePanel(panelID: Int) {

     if let splitViewItem = self.splitViewItems[panelID] as? NSSplitViewItem {

          // Toggle the collapsed state
          NSAnimationContext.runAnimationGroup({ context in

               // special case for the left panel
               if panelID == 0 {
                    var windowFrame = self.view.window.frame
                    let panelWidth = splitViewItem.viewController.view.frame.width
                    if splitViewItem.collapsed {
                         windowFrame.origin.x -= panelWidth
                         windowFrame.size.width += panelWidth
                    } else {
                         windowFrame.origin.x += panelWidth
                         windowFrame.size.width -= panelWidth
                    }
                    self.view.window.animator().setFrame(windowFrame, display: true)
               }
               splitViewItem.animator().collapsed = !splitViewItem.collapsed
          }, completionHandler: nil)
     }
}

I am aware of the "Don't cross the streams" issue (from session 213, WWDC'13) where a window resizing animation running on the main thread and a core animation collapse animation running on a separate thread interfere with each other. Putting the splitViewItem collapse animation onto the main thread seems like the wrong approach and I've got a nagging feeling there's a much better way of doing this that I'm missing.

Since I am not finding any documentation on the NSSplitViewItems anywhere (yet) I would appreciate any insights on this.

I have the little test project on GitHub here if anyone wants a look.

Update The project mentioned has now been updated with the solution.

Thanks, Teo


Solution

  • The problem is similar to the "don't cross the streams" issue in that there are two drivers to the animation you've created: (1) the split view item (2) the window, and they're not in sync.

    In the example from the '13 Cocoa Animations talk, constraints were setup to result in the correct within-window animation as only the window's frame was animated.

    Something similar could be tried here -- only animating the window's frame and not the split view item, but since the item manages the constraints used to (un)collapse, the app can't control exactly how within-window content animates:

    Animating Window frame

    Instead the split view item animation could completely drive the animation and use NSWindow's -anchorAttributeForOrientation: to describe how the window's frame is affected.

    if let splitViewItem = self.splitViewItems[panelID] as? NSSplitViewItem {
        let window = self.view.window
        if panelID == 0 {
            // The Trailing edge of the window is "anchored", alternatively it could be the Right edge
            window.setAnchorAttribute(.Trailing, forOrientation:.Horizontal)
        }
        splitViewItem.animator().collapsed = !splitViewItem.collapsed
    }
    

    Animating SplitViewItem