4

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()
        })
    }

}
Community
  • 1
  • 1
Zelko
  • 3,793
  • 3
  • 34
  • 40
  • Did you ever figure this out? It doesn't require storyboards as the default presentation animations work with nibs. I'd like to do this as a way to finally load a view more easily with less boilerplate – uchuugaka Jan 13 '15 at 18:06
  • @uchuugaka - see my [answer](http://stackoverflow.com/a/28142651/1375695), with accompanying [sample project](https://github.com/foundry/NSViewControllerPresentation) – foundry Jan 26 '15 at 00:15
  • @foundry nice write up! I actually figured it out myself a bit after asking that. I found I t's shockingly simple and shows that it's really just a stock method stub – uchuugaka Jan 26 '15 at 00:19
  • At least for me it worked well just to use it as a holder for a place to load a view and either stick it in a window or popover or whatever and set up the exit strategy. – uchuugaka Jan 26 '15 at 00:20

3 Answers3

7

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:

  • NSStoryboardSegue transitions from one NSViewController to another in the same Storyboard
  • NSViewController present: methods to achieve the same affect to a separate Xib-based NSViewController without using a Storyboard Segue presentViewController:asPopoverRelativeToRect:ofView:preferredEdge:behavior:
    presentViewControllerAsSheet:
    presentViewControllerAsModalWindow:
    presentViewController:animator:
  • animator and segue objects in Objective-C and Swift

enter image description here

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.

enter image description here

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

foundry
  • 31,615
  • 9
  • 90
  • 125
  • 1
    Note that you can use null coalescing instead of using a temporary: `identifier ?? ""` means "`identifier` if it's not nil, otherwise `""`. – zneak Feb 12 '15 at 19:48
  • Above Example Crash on Objective C, [ViewController performSelector:withObject:]: message sent to deallocated instance 0x6000000d06f0, – Sawan Cool Jan 13 '17 at 07:37
1

The same issue comes to me since I forgot make Identity to the segue.

After that, my segue subclass could worked fine.

Shawn Clovie
  • 38
  • 1
  • 5
0

Highly recommend you take a look at the Apple documentation. If you dig into it a bit, you'll notice in the perform method, you can override animations and such:

SWIFT

func perform()

OBJECTIVE-C

- (void)perform

"You can override this method in your NSStoryboardSegue subclass to perform custom animation between the starting/containing controller and the ending/contained controller for a storyboard segue. Typically, you would use Core Animation to set up an animation from one set of views to the next. For more complex animations, you might take a snapshot image of the two view hierarchies and manipulate the images instead of the view objects.*

Regardless of how you perform the animation, you are responsible for installing the destination view controller o window controller (and its contained views) in the right place so that it can handle events. Typically, this entails calling one of the presentation methods in the NSViewController class."

What you might do as well is have a look at some of the iOS UIStoryboardSegue examples out there in the wild and you should find they're quite similar.

Community
  • 1
  • 1
brandonscript
  • 68,675
  • 32
  • 163
  • 220
  • Pardon my lack of understanding… I've closely reviewed the Apple documentation, which is why I referenced it in the original post. Although it is conceptually parallel to the UIKit counterparts, I am failing to work out the final harmony. – Zelko Nov 12 '14 at 00:13
  • Oops, didn't realize you linked to the apple docs sorry ;). Have you got any code so far that isn't working? – brandonscript Nov 12 '14 at 00:13
  • Alright, so what exactly isn't working with it? Are you just unable to use it, or is it behaving unexpectedly? – brandonscript Nov 12 '14 at 00:38
  • Oddly, if I attempt to use it in a storyboard (by specifying it as a custom segue) it immediately crashes upon performing. The crash is generic in nature and does not offer additional insight. Without any official guidance / sample code, I am without recourse. – Zelko Nov 12 '14 at 02:51