I'm trying to add a menu to the app menu through Appkit by using the NSMenu Class. But I'm not sure how to call it so far I've tried:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 512),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
class myMenu: NSMenu {
init(aTitle: "HelloWorld")
}
}
But this only gives me "Expected parameter type following ':'" and "'required' initializer 'init(coder:)' must be provided by subclass of 'NSMenu'".
I use separate functions for buildWnd and buildMenu, but this should work using your technique. Create a new file in your swift project called main.swift and delete the pre-existing AppDelegate. Copy/paste this code into the main.swift file.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
// var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
// let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 512),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.isReleasedWhenClosed = false
window.center()
window.setFrameAutosaveName("Main Window")
// window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
let mainMenu = NSMenu()
NSApp.mainMenu = mainMenu
// **** App menu **** //
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenuItem.submenu = appMenu
appMenu.addItem(withTitle:"Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q")
}
}
let appDelegate = AppDelegate()
// **** Main **** //
let application = NSApplication.shared
application.delegate = appDelegate
//application.activate(ignoringOtherApps:true)
application.run()
Related
All I am trying in this question is about get notified when window itself did load, in order to try solve the issue i make a sub class of NSWindowController and named it as MyNSWindowController and put my needed code for solving the issue, but Xcode make an error of:
Cannot convert value of type 'NSWindowController' to specified type 'MyNSWindowController'
Here is my codes:
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
buildMainMenu()
buildWindow()
}
func buildWindow() {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 100.0, height: 100.0),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.setFrameAutosaveName("Main Window")
window.title = "No Storyboard Window"
window.makeKeyAndOrderFront(window)
window.center()
}
func buildMainMenu() {
let appMainMenu: NSMenu = NSMenu()
let mainMenu: NSMenuItem = NSMenuItem()
mainMenu.submenu = NSMenu(title: "MainMenu")
let mainMenuItem0 = NSMenuItem(title: "About", action: #selector(NSApplication.about), keyEquivalent: "a")
mainMenuItem0.keyEquivalentModifierMask = .command
let mainMenuItem1 = NSMenuItem(title: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
mainMenuItem1.keyEquivalentModifierMask = .command
let mainMenuItem2 = NSMenuItem(title: "Quit", action: #selector(NSApplication.shared.terminate(_:)), keyEquivalent: "q")
mainMenuItem2.keyEquivalentModifierMask = .command
mainMenu.submenu?.items = [mainMenuItem0, mainMenuItem1, mainMenuItem2]
appMainMenu.items = [mainMenu]
NSApp.mainMenu = appMainMenu
}
}
extension NSApplication {
#objc func about() {
AboutView().openInWindow(title: "About My App", sender: self)
}
}
struct AboutView: View {
var body: some View {
Color.green
.frame(width: 500, height: 200)
}
}
extension View {
#discardableResult
func openInWindow(title: String, sender: Any?) -> NSWindow {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 500.0, height: 500.0),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
// let myNSWindowController: NSWindowController = NSWindowController(window: window)
let myNSWindowController: MyNSWindowController = NSWindowController(window: window)
window.makeKeyAndOrderFront(window)
window.center()
window.isReleasedWhenClosed = false
window.setFrameAutosaveName("Main Window")
window.title = "No Storyboard Window"
window.contentView = NSHostingView(rootView: self)
return window
}
}
class MyNSWindowController: NSWindowController {
override func windowDidLoad() {
print("Window did load!")
}
}
If you think my plan is not good about get notified when window get load, you can come with better answer for solving the issue and goal, I have to say I am trying to find a native way in Cocoa to work as .onAppear modifier which we have in SwiftUI. In SwiftUI .onAppear modifier is for content of view, here in this question i just want to know when the window did load or appear, just the window itself not what carry this window.
This codes works well until I want close about window with mouse or using keyboard with command + w, when I close my about window an Error happen:
Error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x20)
Not sure why this error happen and how to solve the issue! By the way I am using a main file for loading my app like this:
import Cocoa
// **** Main **** //
let nsApplication = NSApplication.shared
let appDelegate = AppDelegate()
nsApplication.delegate = appDelegate
nsApplication.run()
And this is my all codes:
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
buildMainMenu()
buildWindow()
}
func buildWindow() {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 100.0, height: 100.0),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.title = "No Storyboard Window"
window.makeKeyAndOrderFront(window)
}
func buildMainMenu() {
let appMainMenu: NSMenu = NSMenu()
let mainMenu: NSMenuItem = NSMenuItem()
mainMenu.submenu = NSMenu(title: "MainMenu")
let mainMenuItem0 = NSMenuItem(title: "About", action: #selector(NSApplication.about), keyEquivalent: "a")
mainMenuItem0.keyEquivalentModifierMask = .command
let mainMenuItem1 = NSMenuItem(title: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
mainMenuItem1.keyEquivalentModifierMask = .command
let mainMenuItem2 = NSMenuItem(title: "Quit", action: #selector(NSApplication.shared.terminate(_:)), keyEquivalent: "q")
mainMenuItem2.keyEquivalentModifierMask = .command
mainMenu.submenu?.items = [mainMenuItem0, mainMenuItem1, mainMenuItem2]
appMainMenu.items = [mainMenu]
NSApp.mainMenu = appMainMenu
}
}
extension NSApplication {
#objc func about() {
AboutView().openInWindow(title: "About My App", sender: self)
}
}
struct AboutView: View {
var body: some View {
Color.green
.frame(width: 500, height: 200)
}
}
extension View {
#discardableResult
func openInWindow(title: String, sender: Any?) -> NSWindow {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.contentView = NSHostingView(rootView: self)
window.title = title
window.makeKeyAndOrderFront(sender)
return window
}
}
One line of code fixes it: window.isReleasedWhenClosed = false added to the function openInWindow(). Reference is here:https://developer.apple.com/forums/thread/651592 . Your original code would destroy the about window object each time it was opened and closed. By not allowing it to do that your code runs without error. Make this substitution.
extension View {
#discardableResult
func openInWindow(title: String, sender: Any?) -> NSWindow {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.isReleasedWhenClosed = false
window.contentView = NSHostingView(rootView: self)
window.title = title
window.makeKeyAndOrderFront(sender)
return window
}
}
So there is a modifier in SwiftUI called .onAppear which allow us to know the view get appeared, I am looking to same functionality in Cocoa macOS, so there is method in AppDelegate in Cocoa called applicationDidFinishLaunching so I am looking for something like that for Windows, let say I am looking something like windowDidFinishLaunching, so I just want window notify me when window did launch or shown, in my question I am lunching a window called about, so how can i know that about window get fully lunched and shown in screen? So the goal is to get notified when about window did lunch or fully shown in screen.
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
buildMainMenu()
buildWindow()
}
func buildWindow() {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 100.0, height: 100.0),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.setFrameAutosaveName("Main Window")
window.title = "No Storyboard Window"
window.makeKeyAndOrderFront(window)
window.center()
}
func buildMainMenu() {
let appMainMenu: NSMenu = NSMenu()
let mainMenu: NSMenuItem = NSMenuItem()
mainMenu.submenu = NSMenu(title: "MainMenu")
let mainMenuItem0 = NSMenuItem(title: "About", action: #selector(NSApplication.about), keyEquivalent: "a")
mainMenuItem0.keyEquivalentModifierMask = .command
let mainMenuItem1 = NSMenuItem(title: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
mainMenuItem1.keyEquivalentModifierMask = .command
let mainMenuItem2 = NSMenuItem(title: "Quit", action: #selector(NSApplication.shared.terminate(_:)), keyEquivalent: "q")
mainMenuItem2.keyEquivalentModifierMask = .command
mainMenu.submenu?.items = [mainMenuItem0, mainMenuItem1, mainMenuItem2]
appMainMenu.items = [mainMenu]
NSApp.mainMenu = appMainMenu
}
}
extension NSApplication {
#objc func about() {
AboutView().openInWindow(title: "About My App", sender: self)
}
}
struct AboutView: View {
var body: some View {
Color.green
}
}
extension View {
#discardableResult
func openInWindow(title: String, sender: Any?) -> NSWindow {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 100.0, height: 100.0),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.makeKeyAndOrderFront(window)
window.center()
window.setFrameAutosaveName("Main Window")
window.title = "No Storyboard Window"
window.contentView = NSHostingView(rootView: self)
return window
}
}
By the way I am using a main file for lunching my application like this:
import Cocoa
// **** Main **** //
let nsApplication = NSApplication.shared
let appDelegate = AppDelegate()
nsApplication.delegate = appDelegate
nsApplication.run()
I have a very simple SwiftUI app that runs in the menu bar and should periodically open an app window (there is only one window/view in the whole app) from inside a repeating timer in the background.
How do I actually open the app window from code?
Here's a simplified AppDelegate.swift example showing what I'm trying to do:
import Cocoa
import SwiftUI
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Create the window and set the content view.
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.contentView = NSHostingView(rootView: contentView)
window.makeKeyAndOrderFront(nil)
var loop = 0
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
loop+=1
if (loop % 10 == 0) {
// TODO: How to close the window?
} else {
// TODO: How to reopen the window?
}
}
}
}
One approach is to hide/unhide the application itself
func applicationDidFinishLaunching(_ aNotification: Notification) {
//setup of window etc ...
Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { _ in
if self.window.isVisible {
NSApp.hide(self)
} else {
NSApp.unhide(self)
}
})
}
I'm newbe in Cocoa Apps development. My fullscreen button is disabled for some unknown reason. Probably the problem is in styleMask. Here is my code:
class AppDelegate: NSObject, NSApplicationDelegate {
lazy var window = NSWindow(contentRect:
NSRect(center: NSScreen.main?.frame.center ?? .zero, size: CGSize(width: 800, height: 450)),
styleMask: [.titled, .miniaturizable, .closable, .fullSizeContentView],
backing: NSWindow.BackingStoreType.buffered,
defer: false)
func applicationDidFinishLaunching(_ aNotification: Notification) {
let vc = Router.getPhotoBrowserNSViewController()
window.contentViewController = vc
window.delegate = NSWindowHandler()
window.titlebarAppearsTransparent = true
window.isMovableByWindowBackground = true
window.makeKeyAndOrderFront(nil)
window.maxFullScreenContentSize = NSScreen.main?.frame.size ?? .zero
window.minFullScreenContentSize = CGSize(width: 800, height: 450)
}
}
And here is the result:
https://imgur.com/NE4fgfG
Any Help would be appreciated.
You need to set the window's collectionBehavior to an option set that includes .fullScreenPrimary.