Open a View from the menu

(Swift, macOS, storyboards)

How to open a view (not a window) by clicking a menu item?

Let's suppose I have one window and two views:

I try to open with Menu item: "open view 1" and item: "open view 2":

(Here I asked the same question. Claude 31 solved very well with a button in one of the views. Here I am asking the same but with a button in the menu or menu item: https://developer.apple.com/forums/thread/690644 )

(Be aware that I am a beginner and I try to learn. Please explain in a simple way if possible)

Accepted Reply

You should better do it in the usual way of macOS programming.

(You have no need to define your own nib, you have no need to define global variables.)

Assuming you already added menu items Open view 1 and Open view 2 as in the screen shot, and the Custom Classes of the two view controllers are FirstViewController and SecondViewController.

(The first may be ViewController, then you may need to replace FirstViewController to ViewController in the following code.)

1. Create a new file MyWindowController.swift and define the IBActions for Open view 1 and Open view 2 in it.

MyWindowController.swift:

import Cocoa

class MyWindowController: NSWindowController {
    
    @IBAction func openView1(_ sender :Any) {
        guard let storyboard = self.storyboard else {
            return
        }
        let firstViewController = storyboard.instantiateController(withIdentifier: "FirstView") as? FirstViewController
        self.window?.contentViewController = firstViewController
    }
    
    @IBAction func openView2(_ sender :Any) {
        guard let storyboard = self.storyboard else {
            return
        }
        let secondViewController = storyboard.instantiateController(withIdentifier: "SecondView") as? SecondViewController
        self.window?.contentViewController = secondViewController
    }
    
}

2. In the Interface Builder, change the Custom Class of your Window Controller to MyWindowController.

3. Connect the action of the menu item Open view 1 to openView1: of First Responder.

(A pop up list is shown when you release the mouse button, you can choose openView1: from the list.)

4. Connect the action of the menu item Open view 2 to openView2: of First Responder as well.


5. Run your code.

If you find something wrong, please tell me. I did test.

  • There must be something I do wrong or do not understand in point 2: I select the Window Controller > Identity Inspector > Class > I cannot change it. I write MyWindowController and when I make intro or go away, it disappears. What am I missing? (in point 1 I have created File > New > Cocoa Class > Class: MyWindowController, Subclass of NSViewController, no XIP selected)

  • When I created a new file with File > New > File... > Cocoa Class > Class: MySecondWindowController, Subclass of NSWindowController, no XIP selected, I could choose Both MyWindowController and MySecondWindowController in the pull down list of Custom Class > Class of the Identity Inspector of the window controller. Not a subclass of NSViewController, it should be a subclass of NSWindowController. And you should go to step 2 after you completely copy my code into MyWindowController.swift, the order of the shown steps have meaning.

  • You may have selected the view and not the window. Click on the bar above the window in IB.

Replies

What I do is declare a global var (in a singleton in fact) to hold the windowController. myWindowController is of a subClass of NSWindowController, with its own nib. In the subClass MyWindowController, I have several init:

class MyWindowController: NSWindowController, NSWindowDelegate {

    override init(window: NSWindow!) {  
        // initialize what you need 
        super.init(window: window)
    }
    
    required init?(coder: (NSCoder?)) {
        // initialize what you need 
        super.init(coder: coder!)  
    }
    
    convenience init(someTag: Int) {  // Need a parameter to differentiate from basic init()
        self.init(windowNibName: NSNib.Name(rawValue: "MyNibName")) // NibName of the window
        // initialize what you need 
    }

}

The global var is initialized (in singleton) with:

myWindowController = MyWindowController(someTag: 0)  // in fact GlobalData.sharedGlobal.myWindowController =

Then the IBAction of menuItem to open the window is simply:

    @IBAction fileprivate func showMyWindow(_ sender: NSMenuItem) {
        myWindowController!.showWindow(self)   // in fact, GlobalData.sharedGlobal.myWindowController!.showWindow(self)
}

Then for the menuItems to change the view in the window:

    @IBAction fileprivate func loadView1(_ sender: NSMenuItem) {
        let storyboard: NSStoryboard = NSStoryboard(name: "Main", bundle: nil)
        let myViewController = storyboard.instantiateController(withIdentifier: "FirstView") as? FirstViewController
        self.view.window?.contentViewController = myViewController
  }

    @IBAction fileprivate func loadView2(_ sender: NSMenuItem) {
        let storyboard: NSStoryboard = NSStoryboard(name: "Main", bundle: nil)
        let myViewController = storyboard.instantiateController(withIdentifier: "SecondView") as? SecondViewController
        self.view.window?.contentViewController = myViewController
  }

Note: I did not test yet, but that should work.

You should better do it in the usual way of macOS programming.

(You have no need to define your own nib, you have no need to define global variables.)

Assuming you already added menu items Open view 1 and Open view 2 as in the screen shot, and the Custom Classes of the two view controllers are FirstViewController and SecondViewController.

(The first may be ViewController, then you may need to replace FirstViewController to ViewController in the following code.)

1. Create a new file MyWindowController.swift and define the IBActions for Open view 1 and Open view 2 in it.

MyWindowController.swift:

import Cocoa

class MyWindowController: NSWindowController {
    
    @IBAction func openView1(_ sender :Any) {
        guard let storyboard = self.storyboard else {
            return
        }
        let firstViewController = storyboard.instantiateController(withIdentifier: "FirstView") as? FirstViewController
        self.window?.contentViewController = firstViewController
    }
    
    @IBAction func openView2(_ sender :Any) {
        guard let storyboard = self.storyboard else {
            return
        }
        let secondViewController = storyboard.instantiateController(withIdentifier: "SecondView") as? SecondViewController
        self.window?.contentViewController = secondViewController
    }
    
}

2. In the Interface Builder, change the Custom Class of your Window Controller to MyWindowController.

3. Connect the action of the menu item Open view 1 to openView1: of First Responder.

(A pop up list is shown when you release the mouse button, you can choose openView1: from the list.)

4. Connect the action of the menu item Open view 2 to openView2: of First Responder as well.


5. Run your code.

If you find something wrong, please tell me. I did test.

  • There must be something I do wrong or do not understand in point 2: I select the Window Controller > Identity Inspector > Class > I cannot change it. I write MyWindowController and when I make intro or go away, it disappears. What am I missing? (in point 1 I have created File > New > Cocoa Class > Class: MyWindowController, Subclass of NSViewController, no XIP selected)

  • When I created a new file with File > New > File... > Cocoa Class > Class: MySecondWindowController, Subclass of NSWindowController, no XIP selected, I could choose Both MyWindowController and MySecondWindowController in the pull down list of Custom Class > Class of the Identity Inspector of the window controller. Not a subclass of NSViewController, it should be a subclass of NSWindowController. And you should go to step 2 after you completely copy my code into MyWindowController.swift, the order of the shown steps have meaning.

  • You may have selected the view and not the window. Click on the bar above the window in IB.