How to expand and collapse NSSplitView subviews with animation?

前端 未结 5 2096
终归单人心
终归单人心 2020-12-23 17:50

Is it possible to animate the collapsing and expanding of NSSplitView subviews? (I am aware of the availability of alternative classes, but would prefer using NSSplitView ov

5条回答
  •  伪装坚强ぢ
    2020-12-23 18:46

    Solution for macOS 10.11.

    Main points:

    1. NSSplitViewItem.minimumThickness depends of NSSplitViewItem .viewController.view width/height, if not set explicitly.

    2. NSSplitViewItem .viewController.view width/height depends of explicitly added constraints.

    3. NSSplitViewItem (i.e. arranged subview of NSSplitView) can be fully collapsed, if it can reach Zero dimension (width or height).

    So, we just need to deactivate appropriate constrains before animation and allow view to reach Zero dimension. After animation we just need to activate needed constraints.

    class SplitViewAnimationsController: ViewController {
    
       private lazy var toolbarView = StackView().autolayoutView()
       private lazy var revealLeftViewButton = Button(title: "Left").autolayoutView()
       private lazy var changeSplitOrientationButton = Button(title: "Swap").autolayoutView()
       private lazy var revealRightViewButton = Button(title: "Right").autolayoutView()
    
       private lazy var splitViewController = SplitViewController()
    
       private lazy var viewControllerLeft = ContentViewController()
       private lazy var viewControllerRight = ContentViewController()
       private lazy var splitViewItemLeft = NSSplitViewItem(viewController: viewControllerLeft)
       private lazy var splitViewItemRight = NSSplitViewItem(viewController: viewControllerRight)
    
       private lazy var viewLeftWidth = viewControllerLeft.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
       private lazy var viewRightWidth = viewControllerRight.view.widthAnchor.constraint(greaterThanOrEqualToConstant: 100)
       private lazy var viewLeftHeight = viewControllerLeft.view.heightAnchor.constraint(greaterThanOrEqualToConstant: 40)
       private lazy var viewRightHeight = viewControllerRight.view.heightAnchor.constraint(greaterThanOrEqualToConstant: 40)
       private lazy var equalHeight = viewControllerLeft.view.heightAnchor.constraint(equalTo: viewControllerRight.view.heightAnchor, multiplier: 1)
       private lazy var equalWidth = viewControllerLeft.view.widthAnchor.constraint(equalTo: viewControllerRight.view.widthAnchor, multiplier: 1)
    
       override func loadView() {
          super.loadView()
          splitViewController.addSplitViewItem(splitViewItemLeft)
          splitViewController.addSplitViewItem(splitViewItemRight)
          contentView.addSubviews(toolbarView, splitViewController.view)
          addChildViewController(splitViewController)
    
          toolbarView.addArrangedSubviews(revealLeftViewButton, changeSplitOrientationButton, revealRightViewButton)
       }
    
       override func viewDidAppear() {
          super.viewDidAppear()
          splitViewController.contentView.setPosition(contentView.bounds.width * 0.5, ofDividerAt: 0)
       }
    
       override func setupDefaults() {
          setIsVertical(true)
       }
    
       override func setupHandlers() {
          revealLeftViewButton.setHandler { [weak self] in guard let this = self else { return }
             self?.revealOrCollapse(this.splitViewItemLeft)
          }
          revealRightViewButton.setHandler { [weak self] in guard let this = self else { return }
             self?.revealOrCollapse(this.splitViewItemRight)
          }
          changeSplitOrientationButton.setHandler { [weak self] in guard let this = self else { return }
             self?.setIsVertical(!this.splitViewController.contentView.isVertical)
          }
       }
    
       override func setupUI() {
    
          splitViewController.view.translatesAutoresizingMaskIntoConstraints = false
          splitViewController.contentView.dividerStyle = .thin
          splitViewController.contentView.setDividerThickness(2)
          splitViewController.contentView.setDividerColor(.green)
    
          viewControllerLeft.contentView.backgroundColor = .red
          viewControllerRight.contentView.backgroundColor = .blue
          viewControllerLeft.contentView.wantsLayer = true
          viewControllerRight.contentView.wantsLayer = true
    
          splitViewItemLeft.canCollapse = true
          splitViewItemRight.canCollapse = true
    
          toolbarView.distribution = .equalSpacing
       }
    
       override func setupLayout() {
          var constraints: [NSLayoutConstraint] = []
    
          constraints += LayoutConstraint.Pin.InSuperView.horizontally(toolbarView, splitViewController.view)
          constraints += [
             splitViewController.view.topAnchor.constraint(equalTo: contentView.topAnchor),
             toolbarView.topAnchor.constraint(equalTo: splitViewController.view.bottomAnchor),
             toolbarView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
          ]
    
          constraints += [viewLeftWidth, viewLeftHeight, viewRightWidth, viewRightHeight]
          constraints += [toolbarView.heightAnchor.constraint(equalToConstant: 48)]
    
          NSLayoutConstraint.activate(constraints)
       }
    }
    
    extension SplitViewAnimationsController {
    
       private enum AnimationType: Int {
          case noAnimation, `default`, rightDone
       }
    
       private func setIsVertical(_ isVertical: Bool) {
          splitViewController.contentView.isVertical = isVertical
          equalHeight.isActive = isVertical
          equalWidth.isActive = !isVertical
       }
    
       private func revealOrCollapse(_ item: NSSplitViewItem) {
    
          let constraintToDeactivate: NSLayoutConstraint
          if splitViewController.splitView.isVertical {
             constraintToDeactivate = item.viewController == viewControllerLeft ? viewLeftWidth : viewRightWidth
          } else {
             constraintToDeactivate = item.viewController == viewControllerLeft ? viewLeftHeight : viewRightHeight
          }
    
          let animationType: AnimationType = .rightDone
    
          switch animationType {
          case .noAnimation:
             item.isCollapsed = !item.isCollapsed
          case .default:
             item.animator().isCollapsed = !item.isCollapsed
          case .rightDone:
             let isCollapsedAnimation = CABasicAnimation()
             let duration: TimeInterval = 3 // 0.15
             isCollapsedAnimation.duration = duration
             item.animations = [NSAnimatablePropertyKey("collapsed"): isCollapsedAnimation]
             constraintToDeactivate.isActive = false
             setActionsEnabled(false)
             NSAnimationContext.runImplicitAnimations(duration: duration, animations: {
                item.animator().isCollapsed = !item.isCollapsed
             }, completion: {
                constraintToDeactivate.isActive = true
                self.setActionsEnabled(true)
             })
          }
       }
    
       private func setActionsEnabled(_ isEnabled: Bool) {
          revealLeftViewButton.isEnabled = isEnabled
          revealRightViewButton.isEnabled = isEnabled
          changeSplitOrientationButton.isEnabled = isEnabled
       }
    }
    
    class ContentViewController: ViewController {
    
       override func viewDidLayout() {
          super.viewDidLayout()
          print("frame: \(view.frame)")
       }
    }
    

提交回复
热议问题