I have an NSDocument subclass that has its own xib file. Also I have an NSViewController subclass with its own xib file too, and I want to present its view modally, like this one.
Problem is, it always shows it as a separate floating window without title bar.
The view I'm trying to present is contained in a window in that xib file. And yes, it's Mac OS X 10.10. Here's the code.
#IBAction func didPressEditF(sender: AnyObject) {
let controller = ViewController(nibName: "ViewController", bundle: nil)
let window = self.windowControllers[0].window as NSWindow
window.beginSheet(controller.view.window, completionHandler: didEndPresentingF)
}
It's OK if you help me using Objective-C.
Alright. I figured it out.
At first. We need a property of our ViewController class so it won't get released after showing.
var controller: ViewController?
Then we need a method that will return a window of the current document. Somehow self.windowControllers[0].window as NSWindow doesn't work.
func window() -> NSWindow {
let windowControllers = self.windowControllers
let controller = windowControllers[0] as NSWindowController
let window = controller.window
return window
}
And finally, the code that opens up the sheet window will look like this:
#IBAction func didPressEditF(sender: AnyObject) {
controller = ViewController(nibName: "ViewController", bundle: nil)
self.window().beginSheet(controller!.view.window, completionHandler: didEndPresentingF)
}
Apple HAS to do something with their outdated documentation.
Instead of digging through the document's window controllers, you could call windowForSheet, a method on NSDocument. E.g. self.windowForSheet.
Related
I am making an app where at the begging it check if a value exist in UserDefaults. If not, it opens a window asking the user to choose where to put a Database and save the path in UserDefaults. If the Path is already hear, then the main window opens.
To do this I have two storyboard, the Main containing all the controllers for the main window and the Welcome storyboard containing the welcome screen.
My main window contains a NSToolbar with items. the problem is that if I put my main window controller as initial controller, the toolbar just work fine, but the Welcome screen is never called. So I've remove the "is initial controller" options, but if check the conditions in applicationDidFinishLaunching in appDelegate, when I instantiate the window from here (and everything works, the right window is open for the right condition), the Toolbar never works (the actions for the buttons are never called, I checked with prints).
Here's my code in appDelegate :
func applicationDidFinishLaunching(_ aNotification: Notification) {
let userDefaults = UserDefaults.standard
if userDefaults.string(forKey: "Database Path") == nil {
let storyboard = NSStoryboard(name: "Welcome", bundle: nil)
let initiate = storyboard.instantiateController(withIdentifier: "Welcome") as! WelcomeWindowController
initiate.window?.makeKeyAndOrderFront(self)
} else {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let initiate = storyboard.instantiateController(withIdentifier: "Main") as! MainWindowController
initiate.window?.makeKeyAndOrderFront(self)
}
}
Is there something I'm missing when I instantiate the main window controller?
I'm trying to load a NSWindow from an NSViewController on OS X and i'm doing the following:
private lazy var discoverable: DiscoverableWindow = {
return DiscoverableWindow.instanceFromNib()
} ()
The static method instanceFromNib() is defined as below:
class func instanceFromNib() -> DiscoverableWindow {
var instance = DiscoverableWindow()
var objects: NSArray?
NSBundle.mainBundle().loadNibNamed("DiscoverableWindow", owner: instance, topLevelObjects: &objects)
return instance
}
I'm using the window to show from my NSViewController:
NSApp.beginSheet(self.discoverable, modalForWindow: NSApplication.sharedApplication().mainWindow!, modalDelegate: nil, didEndSelector: nil, contextInfo: nil)
However, when I load it I see the following:
Is there something i'm doing incorrectly? Why is the NSWindow blank? I read the following on this:
https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html
Cocoa - loadNibNamed:owner:topLevelObjects: from loaded bundle
Cocoa: NSApp beginSheet sets the application delegate?
Why don't you make a window controller to handle the DiscoverableWindow?
Create a subclass of NSWindowController, make sure "Also create xib file for user interface" is selected. Configure your window in the DiscoverableWindowController xib, uncheck "Visible At Launch" on the properties inspector for the window.
Then, in your ViewController:
#IBAction func showSheet(sender: NSButton) {
let discoverableWC = DiscoverableWindowController(windowNibName: "DiscoverableWindowController")
view.window?.beginSheet(discoverableWC.window!, completionHandler: nil)
}
Generally, each window in your app should be managed by its own window controller, let the window controller handle the nib loading and instantiation for you.
Download the sample project here.
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.
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.
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.
How can I visually design a NSView in Xcode? I'm trying to build a statusBar app with a statusMenu only
As you can see above, the object appear but I can't edit it visually, then it go like this as a result:
I attempted to use a XIB file (only have a NSView, no ViewController), but I can't put the NSView in the XIB into the AppDelegate. I tried another ways and managed to use the XIB file, but then I can't use the Storyboard.
Do you have any idea for this situation?
UPDATE
Thank Max for answer my question. I actually tried that before but failed. I tried it again after your comment and there's still no luck. Here is how I did
I created a ViewController then I change its view to the MenuCustomView (NSView)
This is the code in AppDelegate
let statusBar = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
var menuCustomView = NSView()
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
let statusIcon = NSImage(named: "statusIcon")
let statusMenu = NSMenu()
statusBar.image = statusIcon
statusBar.menu = statusMenu
let menuItem = NSMenuItem()
menuItem.title = "title"
var vc = ViewController()
menuCustomView = vc.view
menuItem.view = menuCustomView
statusMenu.addItem(menuItem)
}
Files I have in the project
Even though you don't need the view controller, you can use them to design the views. Create one in the storyboard, edit the view the way you want.
Then you can use the view by creating a viewController out of the storyboard and using its view property to attach the view to the NSStatusBarItem
My words in code:
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let viewController = storyboard?.instantiateControllerWithIdentifier("MyViewController") as! NSViewController
let view = viewController.view
I assume you want to add functioning code to the view. To, for example respond to the button click, you have to subclass the NSViewController and then implement the code you would have added to the AppDelegate.
If you need any more explanation, feel free to write me :)
I have created a new OS X Cocoa Application using the standard Xcode Swift template (using StoryBoards).
I have implemented an IBAction in AppDelegate.swift to handle when the users selects "Open..." from the "File" menu. If the chosen file is a valid image file, I create an NSImage which I then want to display in the view of ViewController.
#IBAction func openFile(sender: NSMenuItem) {
var openPanel = NSOpenPanel()
openPanel.beginWithCompletionHandler { (result :Int) -> Void in
if result == NSFileHandlingPanelOKButton {
if let imageURL = openPanel.URL {
let image = NSImage(contentsOfURL: imageURL)
// PRESENT image IN THE VIEW CONTROLLER
}
}
}
However, I don't see any way to connect to ViewController from AppDelegate. I have only managed to find suggestions that I should look at self.window! in AppDelegate, but there is no such thing as a window in AppDelegate.
Thanks,
Michael Knudsen
It seems that AppDelegate can connect to objects only within Application Scene in a storyboard. If you want to get a ViewController, instantiate it from a storyboard.
sample:
#IBAction func menuAction(sender: AnyObject) {
if let storyboard = NSStoryboard(name: "Main", bundle: nil) {
let controller = storyboard.instantiateControllerWithIdentifier("VC1") as NSViewController
if let window = NSApplication.sharedApplication().mainWindow {
window.contentViewController = controller // just swap
}
}
}
You can access the mainWinow property and the contentViewController property to create a reference to your custom ViewController class. This is similar to the iOS rootViewController property.
let rootViewController = NSApplication.shared().mainWindow?.windowController?.contentViewController as! ViewController
Now you can use this reference to access IBOutlets on your main storyboard from your AppDelegate.
rootViewController.myTextView.textStorage?.mutableString.setString("Cats and dogs.")
This is good for a simple app with one Window with one ViewController.
I was stuck trying to do this same thing recently and managed to get the event I needed to update my view by creating the #IBAction in my ViewController and control dragging to my Application's First Responder (above the menu in my storyboard view).
Here's the question that got me out of the woods:
Application Menu Items Xcode
And thanks to Bluedome for the suggestion to connect it to First Responder's action.
If you control-drag from the menu to the first responder (red cube above menu) and picked an existing action, then you can "responder chain" to your view controller. In my case I attached Open to openFile and then in my view controller I added the following
override var acceptsFirstResponder: Bool {
return true
}
func openFile(sender: NSMenuItem) {
print("In view controller")
}
and it worked without any changes in AppDelegate. Most of the menus are already hooked up to first responder so just add the matching function name in your view controller.
See this comment and this document on Event Handling Basics for more info.
In Swift 5 and accessing new windows array:
#IBAction func menuAction(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()
// The windows in the array are ordered from back to front by window level;
// thus, the last window in the array is on top of all other app windows.
// On app launch, UIApplication.shared.windows.count == 1 anyway.
if let window = UIApplication.shared.windows.last {
window.rootViewController = controller
}
}