Creating a Cocoa application without NIB files

前端 未结 11 677
余生分开走
余生分开走 2020-12-04 10:24

Yes, I know this goes against the whole MVC principle!

However, I\'m just trying to whip up a pretty trivial application - and I\'ve pretty much implemented it. Howe

相关标签:
11条回答
  • 2020-12-04 10:30

    The sample swift code for the autoreleasepool snippet provided above does not work in modern Xcode. Instead, you need to get rid of the @NSApplicationMain in your App Delegate source file, if there is one (Xcode now adds these for new projects), and add a main.swift file that contains the following:

    The top level code sample above no longer works in recent versions of Xcode. Instead use this:

    import Cocoa
    
    let delegate = ExampleApplicationController() //alloc main app's delegate class
    NSApplication.shared().delegate = delegate //set as app's delegate
    
    let ret = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
    
    0 讨论(0)
  • 2020-12-04 10:31

    Don't use NSApplication and NSApp ...

    You just have to specify the class which implements the UIApplicationDelegate protocol :

    UIApplicationMain(argc, argv, nil, @"Name of your class");
    
    0 讨论(0)
  • 2020-12-04 10:33

    This is the method I use in my applications. Sorry for the formatting, I hope you can make it out. I don’t know how to turn off the auto-formatting here.

    Of course there will be no functioning main menu out of this example, that’s far too much code for me to write on a post like this :P - Sorry, out do some research on that ;)

    This should get you started:

    AppDelegate.h

    @interface MyApplicationDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
        NSWindow * window;
    }
    @end
    

    AppDelegate.m

    @implementation MyApplicationDelegate : NSObject
    - (id)init {
        if (self = [super init]) {
            // allocate and initialize window and stuff here ..
        }
        return self;
    }
    
    - (void)applicationWillFinishLaunching:(NSNotification *)notification {
        [window makeKeyAndOrderFront:self];
    }
    
    - (void)dealloc {
        [window release];
        [super dealloc];
    }
    
    @end
    

    main.m

    #import "AppDelegate.h"
    
    int main(int argc, char * argv[]) {
        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        NSApplication * application = [NSApplication sharedApplication];
    
        MyApplicationDelegate * appDelegate = [[[[MyApplicationDelegate]alloc] init] autorelease];
    
        [application setDelegate:appDelegate];
        [application run];
    
        [pool drain];
    
        return EXIT_SUCCESS;
    }
    
    0 讨论(0)
  • 2020-12-04 10:34

    7 years too late to the party, but a bit simpler single file code

    #import <Cocoa/Cocoa.h>
    
    @interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate> {
        NSWindow* window;
    }
    @end
    
    @implementation AppDelegate : NSObject
    - (id)init {
        if (self = [super init]) {
            window = [NSWindow.alloc initWithContentRect: NSMakeRect(0, 0, 200, 200)
                                               styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskClosable
                                                 backing: NSBackingStoreBuffered
                                                   defer: NO];
        }
        return self;
    }
    
    - (void)applicationWillFinishLaunching:(NSNotification *)notification {
        window.title = NSProcessInfo.processInfo.processName;
        [window cascadeTopLeftFromPoint: NSMakePoint(20,20)];
        [window makeKeyAndOrderFront: self];
    }
    
    @end
    
    int main(int argc, const char * argv[]) {
        NSApplication* app = NSApplication.sharedApplication;
        app.ActivationPolicy = NSApplicationActivationPolicyRegular;
        NSMenuItem* item = NSMenuItem.new;
        NSApp.mainMenu = NSMenu.new;
        item.submenu = NSMenu.new;
        [app.mainMenu addItem: item];
        [item.submenu addItem: [[NSMenuItem alloc] initWithTitle: [@"Quit " stringByAppendingString: NSProcessInfo.processInfo.processName] action:@selector(terminate:) keyEquivalent:@"q"]];
        AppDelegate* appDelegate = AppDelegate.new; // cannot collapse this and next line because .dlegate is weak
        app.delegate = appDelegate;
        (void)app.run;
        return 0;
    }
    
    0 讨论(0)
  • 2020-12-04 10:35

    Swift 4 version with NSToolbar and NSMenu (with event handlers instead of delegates):

    File main.swift:

    autoreleasepool {
       // Even if we loading application manually we need to setup `Info.plist` key:
       // <key>NSPrincipalClass</key>
       // <string>NSApplication</string>
       // Otherwise Application will be loaded in `low resolution` mode.
       let app = Application.shared
       app.setActivationPolicy(.regular)
       app.run()
    }
    

    File: Application.swift

    class Application: NSApplication {
    
       private lazy var mainWindowController = MainWindowController()
       private lazy var mainAppMenu = MainMenu()
    
       override init() {
          super.init()
          setupUI()
          setupHandlers()
       }
    
       required init?(coder: NSCoder) {
          super.init(coder: coder) // This will never called.
       }
    }
    
    extension Application: NSApplicationDelegate {
    
       func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
          return true
       }
    
       func applicationDidFinishLaunching(_ aNotification: Notification) {
          mainWindowController.showWindow(nil)
       }
    
    }
    
    extension Application {
    
       private func setupUI() {
          mainMenu = mainAppMenu
       }
    
       private func setupHandlers() {
          delegate = self
          mainAppMenu.eventHandler = { [weak self] in
             switch $0 {
             case .quit:
                self?.terminate(nil)
             }
          }
       }
    
    }
    

    File MainWindowController.swift

    class MainWindowController: NSWindowController {
    
       private (set) lazy var viewController = MainViewController()
       private (set) lazy var mainToolbar = MainToolbar(identifier: NSToolbar.Identifier("ua.com.wavelabs.Decoder:mainToolbar"))
    
       init() {
          let window = NSWindow(contentRect: CGRect(x: 400, y: 200, width: 800, height: 600),
                                styleMask: [.titled, .closable, .resizable, .miniaturizable],
                                backing: .buffered,
                                defer: true)
          super.init(window: window)
    
    
          let frameSize = window.contentRect(forFrameRect: window.frame).size
          viewController.view.setFrameSize(frameSize)
          window.contentViewController = viewController
    
          window.titleVisibility = .hidden
          window.toolbar = mainToolbar
    
          setupHandlers()
       }
    
       required init?(coder: NSCoder) {
          super.init(coder: coder)
       }
    }
    
    extension MainWindowController {
    
       private func setupHandlers() {
          mainToolbar.eventHandler = {
             print($0)
          }
       }
    }
    

    File MainViewController.swift

    class MainViewController: NSViewController {
    
       init() {
          super.init(nibName: nil, bundle: nil)
       }
    
       required init?(coder: NSCoder) {
          fatalError("init(coder:) has not been implemented")
       }
    
       override func loadView() {
          view = NSView()
          view.wantsLayer = true
          view.layer?.backgroundColor = NSColor.magenta.cgColor
       }
    }
    

    File MainToolbar.swift

    class MainToolbar: NSToolbar {
    
       enum Event: Int {
          case toggleSidePanel
       }
    
       let toolbarDelegate = GenericDelegate()
    
       var eventHandler: ((MainToolbar.Event) -> Void)?
    
       override init(identifier: NSToolbar.Identifier) {
          super.init(identifier: identifier)
          setupUI()
          setupHandlers()
       }
    }
    
    extension MainToolbar {
    
       private func setupUI() {
          allowsUserCustomization = true
          autosavesConfiguration = true
          displayMode = .iconOnly
          toolbarDelegate.allowedItemIdentifiers = [.space, .flexibleSpace]
          toolbarDelegate.selectableItemIdentifiers = [.space, .flexibleSpace]
          toolbarDelegate.defaultItemIdentifiers = Event.toolbarIDs + [.flexibleSpace]
       }
    
       private func setupHandlers() {
          delegate = toolbarDelegate
          toolbarDelegate.makeItemCallback = { [unowned self] id, _ in
             guard let event = Event(id: id) else {
                return nil
             }
             return self.makeToolbarItem(event: event)
          }
       }
    
       private func makeToolbarItem(event: Event) -> NSToolbarItem {
          let item = NSToolbarItem(itemIdentifier: event.itemIdentifier)
          item.setHandler { [weak self] in
             guard let event = Event(id: event.itemIdentifier) else {
                return
             }
             self?.eventHandler?(event)
          }
          item.label = event.label
          item.paletteLabel = event.paletteLabel
          if event.image != nil {
             item.image = event.image
          } else if event.view != nil {
             item.view = event.view
          }
          return item
       }
    }
    
    extension MainToolbar.Event {
    
       init?(id: NSToolbarItem.Identifier) {
          guard let event = (MainToolbar.Event.allValues.filter { $0.itemIdentifier == id }).first else {
             return nil
          }
          self = event
       }
    
       static var allValues: [MainToolbar.Event] {
          return [toggleSidePanel]
       }
    
       static var toolbarIDs: [NSToolbarItem.Identifier] {
          return [toggleSidePanel].map { $0.itemIdentifier }
       }
    
       var itemIdentifier: NSToolbarItem.Identifier {
          switch self {
          case .toggleSidePanel: return NSToolbarItem.Identifier("ua.com.wavalabs.toolbar.toggleSidePanel")
          }
       }
    
       var label: String {
          switch self {
          case .toggleSidePanel: return "Toggle Side Panel"
          }
       }
    
       var view: NSView? {
          return nil
       }
    
       var image: NSImage? {
          switch self {
          case .toggleSidePanel: return NSImage(named: NSImage.Name.folder)
          }
       }
    
       var paletteLabel: String {
          return label
       }
    }
    

    File MainMenu.swift

    class MainMenu: NSMenu {
    
       enum Event {
          case quit
       }
    
       var eventHandler: ((Event) -> Void)?
    
       private lazy var applicationName = ProcessInfo.processInfo.processName
    
       init() {
          super.init(title: "")
          setupUI()
       }
    
       required init(coder decoder: NSCoder) {
          super.init(coder: decoder)
       }
    
    }
    
    
    extension MainMenu {
    
       private func setupUI() {
    
          let appMenuItem = NSMenuItem()
          appMenuItem.submenu = appMenu
    
          addItem(appMenuItem)
       }
    
       private var appMenu: NSMenu {
          let menu = NSMenu(title: "")
          menu.addItem(title: "Quit \(applicationName)", keyEquivalent: "q") { [unowned self] in
             self.eventHandler?(.quit)
          }
          return menu
       }
    
    }
    

    Convenience extensions.

    File NSMenu.swift

    extension NSMenu {
    
       @discardableResult
       public func addItem(title: String, keyEquivalent: String, handler: NSMenuItem.Handler?) -> NSMenuItem {
          let item = addItem(withTitle: title, action: nil, keyEquivalent: keyEquivalent)
          item.setHandler(handler)
          return item
       }
    
    }
    

    File NSMenuItem.swift

    extension NSMenuItem {
    
       public typealias Handler = (() -> Void)
    
       convenience init(title: String, keyEquivalent: String, handler: Handler?) {
          self.init(title: title, action: nil, keyEquivalent: keyEquivalent)
          setHandler(handler)
       }
    
       public func setHandler(_ handler: Handler?) {
          target = self
          action = #selector(wavelabsActionHandler(_:))
          if let handler = handler {
             ObjCAssociation.setCopyNonAtomic(value: handler, to: self, forKey: &OBJCAssociationKeys.actionHandler)
          }
       }
    
    }
    
    extension NSMenuItem {
    
       private struct OBJCAssociationKeys {
          static var actionHandler = "com.wavelabs.actionHandler"
       }
    
       @objc private func wavelabsActionHandler(_ sender: NSControl) {
          guard sender == self else {
             return
          }
          if let handler: Handler = ObjCAssociation.value(from: self, forKey: &OBJCAssociationKeys.actionHandler) {
             handler()
          }
       }
    }
    

    File NSToolbar.swift

    extension NSToolbar {
    
       class GenericDelegate: NSObject, NSToolbarDelegate {
    
          var selectableItemIdentifiers: [NSToolbarItem.Identifier] = []
          var defaultItemIdentifiers: [NSToolbarItem.Identifier] = []
          var allowedItemIdentifiers: [NSToolbarItem.Identifier] = []
    
          var eventHandler: ((Event) -> Void)?
          var makeItemCallback: ((_ itemIdentifier: NSToolbarItem.Identifier, _ willBeInserted: Bool) -> NSToolbarItem?)?
       }
    }
    
    extension NSToolbar.GenericDelegate {
    
       enum Event {
          case willAddItem(item: NSToolbarItem, index: Int)
          case didRemoveItem(item: NSToolbarItem)
       }
    }
    
    extension NSToolbar.GenericDelegate {
    
       func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier,
                    willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
          return makeItemCallback?(itemIdentifier, flag)
       }
    
       func toolbarDefaultItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
          return defaultItemIdentifiers
       }
    
       func toolbarAllowedItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
          return allowedItemIdentifiers
       }
    
       func toolbarSelectableItemIdentifiers(_: NSToolbar) -> [NSToolbarItem.Identifier] {
          return selectableItemIdentifiers
       }
    
       // MARK: Notifications
    
       func toolbarWillAddItem(_ notification: Notification) {
          if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem,
             let index = notification.userInfo?["newIndex"] as? Int {
             eventHandler?(.willAddItem(item: toolbarItem, index: index))
          }
       }
    
       func toolbarDidRemoveItem(_ notification: Notification) {
          if let toolbarItem = notification.userInfo?["item"] as? NSToolbarItem {
             eventHandler?(.didRemoveItem(item: toolbarItem))
          }
       }
    }
    

    File NSToolbarItem.swift

    extension NSToolbarItem {
    
       public typealias Handler = (() -> Void)
    
       public func setHandler(_ handler: Handler?) {
          target = self
          action = #selector(wavelabsActionHandler(_:))
          if let handler = handler {
             ObjCAssociation.setCopyNonAtomic(value: handler, to: self, forKey: &OBJCAssociationKeys.actionHandler)
          }
       }
    
    }
    
    extension NSToolbarItem {
    
       private struct OBJCAssociationKeys {
          static var actionHandler = "com.wavelabs.actionHandler"
       }
    
       @objc private func wavelabsActionHandler(_ sender: NSControl) {
          guard sender == self else {
             return
          }
          if let handler: Handler = ObjCAssociation.value(from: self, forKey: &OBJCAssociationKeys.actionHandler) {
             handler()
          }
       }
    }
    

    File ObjCAssociation.swift

    public struct ObjCAssociation {
    
       public static func value<T>(from object: AnyObject, forKey key: UnsafeRawPointer) -> T? {
          return objc_getAssociatedObject(object, key) as? T
       }
    
       public static func setAssign<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
          objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_ASSIGN)
       }
       public static func setRetainNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
          objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
       }
       public static func setCopyNonAtomic<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
          objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY_NONATOMIC)
       }
       public static func setRetain<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
          objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN)
       }
       public static func setCopy<T>(value: T?, to object: Any, forKey key: UnsafeRawPointer) {
          objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_COPY)
       }
    }
    
    0 讨论(0)
  • 2020-12-04 10:37

    Here is Casper's solution, updated for ARC as per Marco's suggestion:

    #import <Cocoa/Cocoa.h>
    #import "AppDelegate.h"
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            NSApplication *application = [NSApplication sharedApplication];
            AppDelegate *appDelegate = [[AppDelegate alloc] init];
            [application setDelegate:appDelegate];
            [application run];
        }
        return EXIT_SUCCESS;
    }
    
    0 讨论(0)
提交回复
热议问题