OS X Yosemite introduced NSStoryboardSegue
“A storyboard segue specifies a transition or containment relationship between two scenes in a storyboard…”
Update:
• If I attempt to use a NSStoryboardSegue subclass in a Storyboard with Yosemite., it crashes with SIGABRT.
• If I ignore segues, and manually present a view controller using a specified, custom animator for presentation and dismissal,
func presentViewController(_ viewController: NSViewController,
animator animator: NSViewControllerPresentationAnimator)
it works as expected.
This post provides additional insight: Animate custom presentation of ViewController in OS X Yosemite
Using that as a reference, here's my attempt so far:
class FadeSegue: NSStoryboardSegue {
override func perform() {
super.perform()
sourceController.presentViewController(destinationController as NSViewController,
animator: FadeTransitionAnimator())
}
}
class FadeTransitionAnimator: NSObject, NSViewControllerPresentationAnimator {
func animatePresentationOfViewController(toViewController: NSViewController, fromViewController: NSViewController) {
toViewController.view.wantsLayer = true
toViewController.view.layerContentsRedrawPolicy = .OnSetNeedsDisplay
toViewController.view.alphaValue = 0
fromViewController.view.addSubview(toViewController.view)
toViewController.view.frame = fromViewController.view.frame
NSAnimationContext.runAnimationGroup({ context in
context.duration = 2
toViewController.view.animator().alphaValue = 1
}, completionHandler: nil)
}
func animateDismissalOfViewController(viewController: NSViewController, fromViewController: NSViewController) {
viewController.view.wantsLayer = true
viewController.view.layerContentsRedrawPolicy = .OnSetNeedsDisplay
NSAnimationContext.runAnimationGroup({ (context) -> Void in
context.duration = 2
viewController.view.animator().alphaValue = 0
}, completionHandler: {
viewController.view.removeFromSuperview()
})
}
}
The problem appears to be with the Swift 'subclassing' of NSStoryboardSegue. If you implement the same functionality using Objective-C, everything works as expected. The problem is specifically with your FadeSeque
class. The animator object works fine in either Objective-C or Swift.
So this:
class FadeSegue: NSStoryboardSegue {
override func perform() {
super.perform()
sourceController.presentViewController(destinationController as NSViewController,
animator: FadeTransitionAnimator())
}
}
Will work if provided as an Objective-C class:
@interface MyCustomSegue : NSStoryboardSegue
@end
@implementation FadeSegue
- (void)perform {
id animator = [[FadeTransitionAnimator alloc] init];
[self.sourceController presentViewController:self.destinationController
animator:animator];
}
@end
(I don't think you need to call super
)
As this doesn't seem to be documented much anywhere, I have made a small project on github to demonstrate:
presentViewController:asPopoverRelativeToRect:ofView:preferredEdge:behavior:
presentViewControllerAsSheet:
presentViewControllerAsModalWindow:
presentViewController:animator:
edit
OK I've tracked down the EXC_BAD_ACCESS issue. Looking in the stack trace it seemed to have something to do with (Objective-C) NSString to (Swift) String conversion.
That made wonder about the identifier
property of NSStoryboardSegue
. This is used when setting up segues in the Storyboard, and is not so useful in Custom segues created in code. However, it turns out that if you set an identifier in the storyboard to any string value, even "", the crash disappears.
The identifier
property is an NSString* in Objective-C
@property(readonly, copy) NSString *identifier
and an optional String in Swift:
var identifier: String? { get }
Note the read-only status. You can only set the identifier on initialising the object.
The designator initialiser for NSStoryboardSegue
looks like this in Objective-C:
- (instancetype)initWithIdentifier:(NSString *)identifier
source:(id)sourceController
destination:(id)destinationController
and in Swift:
init(identifier identifier: String,
source sourceController: AnyObject,
destination destinationController: AnyObject)
Note the non-optional requirement in the Swift initialiser. Therein lies the problem and the crash. If you don't deliberately set an identifier in the storyboard, the Custom segue's designated initialiser will be called using a nil value for the identifier. Not a problem in Objective-C, but bad news for Swift.
The quick solution is to ensure you set an identifier string in Storyboard. For a more robust solution, it turns out that you can override the designated initialiser in your custom subclass to intercept a nil-valued string. Then you can fill it in with a default value before passing on to super's designated initialiser:
override init(identifier: String?,
source sourceController: AnyObject,
destination destinationController: AnyObject) {
var myIdentifier : String
if identifier == nil {
myIdentifier = ""
} else {
myIdentifier = identifier!
}
super.init(identifier: myIdentifier,
source: sourceController,
destination: destinationController)
}
I have updated the sample project to reflect this solution
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With