how to presentViewControllerAsSheet on OSX Mavericks?

2.4k views Asked by At

It's a long story, but to cut it short; my first OSX app was written (on Yosemite) in Swift using a storyboard until I found out my (finished) app will not run on Mavericks. I need to run on Mavericks, so I have replaced the storyboard with NIBs.

My problem is with the segues; I was using 'sheet type' segues to show other view controllers in a sheet over the main view controller. A call to the presentViewControllerAsSheet method of NSViewController is a good replacement as it looks the same, but this API was introduced in Yosemite - so I need to work out how to do this for Mavericks.

In the action for a button on the main view, I've tried using beginSheet like this:

secondViewController = SecondViewController(nibName: "SecondViewController", bundle: nil)
self.view.window?.beginSheet(secondViewController!view.window!, completionHandler: nil)

But the second view controller's window is null at runtime. I've tried adding the new view controller as a subview to the application window but this is an unrecognised selector:

NSApplication.sharedApplication().windows[0].addSubView(secondViewController!.view)

I've search high and low for a description of how to show a sheet and all I can find is: Can a view controller own a sheet? but I'm sorry to admit I don't understand the answer. Can anybody help me with some working code? I'm beginning to worry that I'm trying to do something unusual but it looks OK on Yosemite, so how did people do this before Yosemite was released?

EDIT I still haven't got to the solution, so I have put together a small app which shows the problems I'm having.

In AppDelegate.swift:

class AppDelegate: NSObject, NSApplicationDelegate {
    @IBOutlet weak var window: NSWindow!
    var mainViewController: FirstView!   
    func applicationDidFinishLaunching(aNotification: NSNotification) {
        mainViewController = FirstView(nibName:"FirstView", bundle: nil)
        window.contentView = mainViewController.view
        mainViewController.view.frame = (window.contentView as! NSView).bounds
    }
}

In FirstView.swift (associated NIB has a 'open sheet' button)

class FirstView: NSViewController {
    var secondView: SecondView?
    var secondWindow: SecondWinCon?    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func pressButton(sender: AnyObject) {
        secondView = SecondView(nibName: "SecondView", bundle: nil)!
// method 1 - this is the behaviour I want (but it only works on OSX 10.10)
//        presentViewControllerAsSheet(secondView!)
// method 2 - this just creates a floating window
//        self.view.addSubview(secondView!.view)
//        self.view.window?.beginSheet(secondView!.view.window!, completionHandler: nil)
// method 3 - this also creates a floating window
        secondWindow = SecondWinCon(windowNibName: "SecondWinCon")
        self.view.window?.beginSheet(secondWindow!.window!, completionHandler: nil)   
    }    
}

In SecondView.swift (associated NIB has a 'close' button)

class SecondView: NSViewController {   
    override func viewDidLoad() {
        super.viewDidLoad()
    }       
    @IBAction func dismissPressed(sender: AnyObject) {
        if (presentingViewController != nil) {
            presentingViewController?.dismissViewController(self)
        } else {
            self.view.window?.sheetParent?.endSheet(self.view.window!)
        }
    }
}

In SecondWinCon.swift (Associated NIB is empty)

class SecondWinCon: NSWindowController {
    var secondView: SecondView?
    override func windowDidLoad() {
        super.windowDidLoad()   
        secondView = SecondView(nibName: "SecondView", bundle: nil)!
        self.window?.contentView.addSubview(secondView!.view)
    }   
}

If method 1 is uncommented, you will see the behaviour I'm trying to emulate (remember it only works on OS X 10.10). Method 2 or 3 displays the second view, but not as a sheet.

2

There are 2 answers

1
DB1 On BEST ANSWER

If you get here looking for a solution, I was nearly there with method 3. The important step I had missed was to turn off "Visible At Launch" in the NSWindowController's NIB (it's an attribute of the NSWindow). In my sample code, this was in SecondWinCon.nib.

0
reviver On

I have the same problem, and found maybe is't an issue related to view life cycle. When I call presentViewControllerAsSheet in viewDidLoad, sheet will not shown, and you will get this in console:

Failed to set (contentViewController) user defined inspected property on (NSWindow): presentViewController:animator:: View '''s view is not in a window/view hierarchy.

If you trigger this in viewWillAppear or viewDidAppear, it's totally no problem.

UPDATE

Okay, let's make it clear.

For this initial storyboard, NSWindowController is connected with a view controller, think this as a root view controller (RootVC). Create another view controller desired as a sheet in storyboard (SheetVC). in viewWillAppear or viewDidAppear of RootVC, [self presentViewControllerAsSheet: SheetVC]

The sheet will show, no additional code required.