3

Overall goal: Use a custom storyboard segue on macOS

The issue at hand is the responder chain. According to Apple:

In addition, in macOS 10.10 and later, a view controller participates in the responder chain. NSViewController

If I use a standard segue in a macOS storyboard, the destination view controller behaves as expected — focus, key equivalents, responder chain, etc.

However, if I use a custom segue, the destination view controller does not behave as expected :( Specifically, key equivalents mysteriously fail to work — However, in testing, key equivalents on buttons from the source view controller continue to work (which isn't helpful/desired)

On macOS 10.12, I'm using the following custom segue… What am I doing wrong?

class PushSegue: NSStoryboardSegue {

override func perform() {
    (sourceController as AnyObject).presentViewController(destinationController as! NSViewController, animator: PushAnimator())
}
}


class PushAnimator:  NSObject, NSViewControllerPresentationAnimator  {

func animatePresentation(of viewController: NSViewController, from fromViewController: NSViewController) {
    viewController.view.wantsLayer = true
    viewController.view.frame = CGRect(x: fromViewController.view.frame.size.width, y: 0,
                                       width: fromViewController.view.frame.size.width, height: fromViewController.view.frame.size.height)
    fromViewController.addChildViewController(viewController)
    fromViewController.view.addSubview(viewController.view)

    let futureFrame = CGRect(x: 0, y: 0, width: viewController.view.frame.size.width,
                             height: viewController.view.frame.size.height)

    NSAnimationContext.runAnimationGroup({ context in
        context.duration = 0.75
        viewController.view.animator().frame = futureFrame

    }, completionHandler:nil)
}

func animateDismissal(of viewController: NSViewController, from fromViewController: NSViewController) {
    let futureFrame = CGRect(x: viewController.view.frame.size.width, y: 0,
                             width: viewController.view.frame.size.width, height: viewController.view.frame.size.height)

    NSAnimationContext.runAnimationGroup({ context in
        context.duration = 0.75
        context.completionHandler = {
            viewController.view.removeFromSuperview()
            viewController.removeFromParentViewController()
        }

        viewController.view.animator().frame = futureFrame
    }, completionHandler: nil)
    
}
}
Community
  • 1
  • 1
Zelko
  • 3,793
  • 3
  • 34
  • 40
  • what is the incorrect behavior that you get? And what is the behavior that you expect? You should specify these things in your question. "actions, buttons, etc" doesn't explain it. I tested your code and it seems to segue just fine (although I would add some aesthetic fixes). So what is the actual problem? – Zion Apr 19 '17 at 12:35
  • Thanks ✌️ I'll improve the question. The issue at hand is the responder chain: specifically, if I present a view controller using a standard storyboard segue, everything works as expected — for example, a button's key equivalent actually works. This is in stark contrast to one of my own custom segues — the same button with the key equivalent doesn't work :( – Zelko Apr 19 '17 at 20:16
  • I tried searching "macos key equivalents" but I'm not finding anything specific. Could you elaborate on what you mean by "key equivalent"? – Zion Apr 19 '17 at 22:13
  • [NSButton](https://developer.apple.com/reference/appkit/nsbutton) @ZionPerez – Zelko Apr 19 '17 at 22:53
  • [The key-equivalent character of the button](https://developer.apple.com/reference/appkit/nsbutton/1525368-keyequivalent) @ZionPerez – Zelko Apr 19 '17 at 22:54
  • I've posted a working solution in my answer below. Again, the custom segue and animator you posted seems to work fine. I only added a red background to it. Maybe you need to show the code that you're using to add key equivalents. – Zion Apr 20 '17 at 11:30
  • You'll need to be more specific on what exactly you're trying to accomplish. What exactly is the issue you're having with the responder chain and focus? Please see my updated answer. – Zion Apr 21 '17 at 15:52

1 Answers1

8

From the way I understand it, your question has four parts:

  1. How to use a custom segue class
  2. An issue with KeyEquivalents
  3. An issue with the Responder Chain
  4. An issue with focus

Solution Demo

I have the following example functioning correctly with your custom segue class, a key equivalents example, a responder chain example, and a focus example (assuming you mean TextField focus).

As you can see in the gif below, you can click the Segue button to transition using your PushSegue segue. Then click the Dismiss button to go back. The right and left arrow are the key equivalents for the Segue and Dismiss buttons respectively. The responder chain successfully passes data from the SheetController (red screen) to the MainViewController (gray screen). Also, the textFields properly get focused when segueing. I'll explain each feature in further detail.

enter image description here

PushSegue

I took your PushAnimator subclass that you posted and just added a red background to the view so that I could see it visually. I did this by adding the following line within the animatePresentation method of your PushAnimator class:

viewController.view.layer?.backgroundColor = CGColor.init(red: 1, green: 0, blue: 0, alpha: 1)

Key Equivalents

The Segue button is assigned the keyEquivalent of NSRightArrowFunctionKey and the Dismiss button is assigned NSLeftArrowFunctionKey. You can tap the right and left arrow keys to segue between the two views. To setup the right arrow, I added the following in the MainViewController viewDidLoad() function:

let array = [unichar(NSRightArrowFunctionKey)]
mainViewSegueButton.keyEquivalent = String(utf16CodeUnits: array, count: 1)

To setup the left arrow, I added the following in the SheetController viewDidLoad() function:

let array = [unichar(NSLeftArrowFunctionKey)]
dismissButton.keyEquivalent = String(utf16CodeUnits: array, count: 1)

Responder Chain

Since you did not describe the specific issue you're having with the responder chain, I just implemented a simple test to see if it works. I will set SheetController's dismissButton with a nextResponder of MainViewController. I have the following in SheetController's viewDidLoad():

// SheetController viewDidLoad() 
// Set Accessibility Label
dismissButton.setAccessibilityLabel("DismissButton")

// Set button target and action        
dismissButton.target = nil
dismissButton.action = #selector(MainViewController.dismissAction(_:))
        
// Set nextResponder
dismissButton.nextResponder = self.parent

To explain the above: I first set up the dismissButton with an accessibility label so that MainViewController can identify the button later. Setting the button's target to nil means that it will look towards its nextResponder to handle any events that it detects. I set the button's action to the dismissAction method, which is declared within MainViewController. And finally, I set the dismissButton's nextResponder to MainViewController.

Back in the MainViewController, I set up the dismissAction method below. It just increments a counter variable and displays that counter along with the button's accessibilityLabel (same access label that we setup earlier) to the textField. The final line dismisses the SheetController.

// MainViewController
func dismissAction(_ sender: AnyObject) {
    counter += 1
    let buttonAccessLabel = (sender as! NSButton).accessibilityLabel()
    helloWorldTextField.stringValue = "Action #" + counter.description + " from: " + buttonAccessLabel!
    self.dismissViewController(self.presentedViewControllers!.first!)
}

Focus

For this, I assume you're referring to textField focus. In SheetController, I have the sheetFocusTextField focus in viewDidAppear:

override func viewDidAppear() {
    super.viewDidAppear()
    sheetFocusTextField.becomeFirstResponder()
}

In MainViewController, I have mainFocusTextField focus in the dismissAction method:

mainFocusTextField.becomeFirstResponder()

Github Link

https://github.com/starkindustries/mac-os-segue-test

References

Zion
  • 1,562
  • 13
  • 32