How to connect code to Mac OS menus (File, Edit,...)

I am new to Mac OS programming. I have an Swift app I would like to create and the user will need to select a group of files/folders. I have done this using to select files that I can use in the app. I have managed to do this using a NSOpenPanel and modal window. I would prefer to do this using the File menu. I have found the Application Scene in Main.Storyboard but I have been unable to find any resources that desvcribe how I can connect code to the menu and then use it to open the folders/files as the openPanel does.


Frankly most of the searching I have done turns up iOS content which does me no good, so I am questioning my ability to even frame my questions properly for search engines.


Can anyone point me to decent resource(s) ?


Thanks,

Chip

Accepted Reply

In this case, I was not using storyboard (I do not use storyboards for MacOS apps), hence I had to recreate several things manually.


And at this point I start becoming confused. So do I need to create an IBOutlet for the AppDelegate in the AppDelegate Class?? Also which menu item am I connecting to the newDossier function and how do I perform this connection? Do I simply use the items like menuBar, in this function?


In fact, the IBOutlet were created to allow for changing the menu item. May not need to start with, but if you want to disable…

With storyboard, no need to create the appDelegate; they are here and connected as needed.


So, you can create a new function,

- select the menu item (File>New) in storyboard

- open connections (connection isnpector or right clock)

- disconnect the IBAction (click on the small x in front of newDocument)

- control drag from the menuItem (in stroryboard) to the appDemegate to create the new action

- this is where you will have the code


This will replace the NSApp.newDocument() func

Replies

You are right, it is not explained correctly anywhere (at least did not find).


My set up for this:


MenuBar is defined in Menu.xib.


I create an AppController class, in which I define the open function


class AppDelegate: NSObject, NSApplicationDelegate {

    @IBAction func newDossier(_ sender: NSMenuItem) {
     // NSPanel stuff
     }

}


In this class, I define and connect the IBOutlets for menus, as


    // MARK: outlets for menus
    @IBOutlet weak fileprivate var menuBar: NSMenu!
  
    // -------------------- Menu File
    @IBOutlet weak fileprivate var fileMenu                 : NSMenu!       //  structure of menu
    @IBOutlet weak fileprivate var fileMenuItem             : NSMenuItem!   // item of menuBar
    @IBOutlet weak var newDocumentMenuItem                  : NSMenuItem!   // used by AppDelegate
    @IBOutlet weak var openDocumentMenuItem                 : NSMenuItem!   // used by AppDelegate
    @IBOutlet weak fileprivate var saveDocumentAsMenuItem   : NSMenuItem!



In Menu.xib, I create an object controller (in IB) and give it the class AppDelegate


In AppDelegate, I connect the IBOutlet to the AppDelegate object of Menu.xib


    @IBOutlet weak var appController: AppController!


Finally, I connect the Menu item from Menu.xib to the IBAction NewDossier in AppDelegate


Tell if something not clear or not working as expected (hope I did not forget a step).

Thanks for the reply.

What I see in Xcode is not quite what you are describing, (no .xib files) but I think I am on the right track. So I have a few questions to get me a point to start playing with this more.


First, when I created the project, Xcode configured it with an AppDelegate class and a file named Main.storyboard as well as a ViewController. I added a few classes to implement my project model.

.

The storyboard includes an Application Scene, Window Controller Scene, and View Controller Scene.

As you can see the Menu Bar is in the Application Scene. So at this point I am not using a .xib file (though perhaps I am and just don't realize it). There is also an AppDelegate in the Application Scene.


So I have added the code you mentioned to the AppDelegate (didn't create a new one). This includes IBOutlets for the menu bar, file menu, and file open menu item and I connected them to the items in the StoryBoard.


And at this point I start becoming confused. So do I need to create an IBOutlet for the AppDelegate in the AppDelegate Class?? Also which menu item am I connecting to the newDossier function and how do I perform this connection? Do I simply use the items like menuBar, in this function?


Also I did run the code as implemented and the Open menu item was greyed out. Suspect I needed to do something to activate it.


//
//  AppDelegate.swift
//  BrackettedPhotoFinder
//
//  Created by Develop on 5/25/19.
//  Copyright © 2019 Develop. All rights reserved.
//

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    // MARK: outlets for menus

    @IBOutlet weak fileprivate var menuBar: NSMenu!
    
    
    // File Menu
    @IBOutlet weak fileprivate var fileMenu: NSMenu! //  structure of menu
    @IBOutlet weak var openDocumentMenuItem: NSMenuItem! // used by AppDelegate

    //@IBOutlet weak fileprivate var fileMenu                 : NSMenu!
    //@IBOutlet weak fileprivate var fileMenuItem             : NSMenuItem!   // item of menuBar
    //@IBOutlet weak var newDocumentMenuItem                  : NSMenuItem!   // used by AppDelegate
    //@IBOutlet weak var openDocumentMenuItem                 : NSMenuItem!
    //@IBOutlet weak fileprivate var saveDocumentAsMenuItem   : NSMenuItem!

    // Original functions in this class
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }
    
    // MARK: My functions to tie into menus
    /* This code comes from a response to a question I submitted on the forums
     * and answered by Claude31.
     */
    @IBAction func newDossier(_ sender: NSMenuItem)  {
        // NSPanel stuff
        print("In newDossier")
    }


}


Thanks again for your insights,


Chip




In this case, I was not using storyboard (I do not use storyboards for MacOS apps), hence I had to recreate several things manually.


And at this point I start becoming confused. So do I need to create an IBOutlet for the AppDelegate in the AppDelegate Class?? Also which menu item am I connecting to the newDossier function and how do I perform this connection? Do I simply use the items like menuBar, in this function?


In fact, the IBOutlet were created to allow for changing the menu item. May not need to start with, but if you want to disable…

With storyboard, no need to create the appDelegate; they are here and connected as needed.


So, you can create a new function,

- select the menu item (File>New) in storyboard

- open connections (connection isnpector or right clock)

- disconnect the IBAction (click on the small x in front of newDocument)

- control drag from the menuItem (in stroryboard) to the appDemegate to create the new action

- this is where you will have the code


This will replace the NSApp.newDocument() func

Thanks Claude31 I did get this to work thanks to your help, simpler than I was thinking it was in my head. Here is what I did.


First I created a function called openDir as an IBAction. To test I placed this in my ViewController.swift file.

@IBAction func openDir(_ openMenuItem: NSMenuItem) {

}


Next I went to Main.storyboard file and selected the File Open... menuItem in the Application Scene as shown below and holding down the control key I dragged the menu item onto the First Responder icon (red box icon at the very top). Then a window opened and I scrolled until I found the name of the openDir function I created. You can see that the linkage between the action and the function has been created in Connection Inspector the next screenshot.


Now when I run my code and use the Open... menu my code enters this function. When I started this I figured an Open Dialog would also be opened and I would be receiving the results of that in the function. That isn't the case I need to do that myself, not a big deal I had already discovered how to do that using an NSOpenPanel class object as shown below. Obviously this was confiugured for my use but hopefully others reading this get the idea.

@IBAction func openDir(_ openMenuItem: NSMenuItem) {
        let openPanel = NSOpenPanel()
        openDocumentMenuItem = openMenuItem
        openPanel.canChooseFiles = false
        openPanel.canChooseDirectories = true
        openPanel.canCreateDirectories = false
        let i = openPanel.runModal()
        if i == NSApplication.ModalResponse.OK {
            print("The directory selected is " + openPanel.url!.absoluteString)
        }
}


Thanks again Claude31 for replying and helping me get this working.

At beginning, creating a MacOS app was a bit tricky. How to start with ? And not much help in doc.


But once you heve started, it's reall a breeze. However, I do not use storyboards for MacOS. I find them really ill adpated.

I get the sense that you are not alone when it comes to your feelings about Storyboards. At this point storyboards are all I know and I want to at least understand how to use them before I jump into a second approach to writing MacOS & iOS apps.


I am also wondering how SwiftUI fits into this picture too.


At least I am having fun!

I mean storyboards for OSX. But for IOS, they are absolutely magic.