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)
}
}
From the way I understand it, your question has four parts:
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
PushSeguesegue. 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 theSheetController(red screen) to theMainViewController(gray screen). Also, the textFields properly get focused when segueing. I'll explain each feature in further detail.PushSegue
I took your
PushAnimatorsubclass 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 theanimatePresentationmethod of yourPushAnimatorclass:Key Equivalents
The Segue button is assigned the keyEquivalent of
NSRightArrowFunctionKeyand the Dismiss button is assignedNSLeftArrowFunctionKey. 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 theMainViewControllerviewDidLoad()function:To setup the left arrow, I added the following in the
SheetControllerviewDidLoad()function: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'sdismissButtonwith a nextResponder ofMainViewController. I have the following inSheetController'sviewDidLoad():To explain the above: I first set up the
dismissButtonwith an accessibility label so thatMainViewControllercan 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 thedismissActionmethod, which is declared withinMainViewController. And finally, I set thedismissButton's nextResponder toMainViewController.Back in the
MainViewController, I set up thedismissActionmethod 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 theSheetController.Focus
For this, I assume you're referring to textField focus. In
SheetController, I have thesheetFocusTextFieldfocus inviewDidAppear:In
MainViewController, I havemainFocusTextFieldfocus in thedismissActionmethod:Github Link
https://github.com/starkindustries/mac-os-segue-test
References