【问题标题】:Creating a Cocoa application without NIB files创建没有 NIB 文件的 Cocoa 应用程序
【发布时间】:2011-03-01 03:40:06
【问题描述】:

是的,我知道这违背了整个 MVC 原则!

但是,我只是想创建一个非常简单的应用程序——而且我已经实现了它。但是,我有一个问题......

我创建了一个空项目,复制所有框架并设置构建设置 - 我收到有关可执行文件的错误,或者缺少可执行文件。构建设置看起来都很好,但它告诉我没有可执行文件-它将构建+运行良好。但是它不运行。也没有错误 - 它似乎运行得非常快而且干净!除非我尝试运行 GDB,它礼貌地告诉我我需要先给它一个文件..

Running…  
No executable file specified.  
Use the "file" or "exec-file" command.

所以我创建了一个 Cocoa 应用程序,删除了所有我不需要的东西(即MainMenu.xib 文件..),现在我可以完美地编译我的代码了。然而它死去抱怨它是

“无法加载 nib 文件:MainMenu,正在退出”

我浏览了项目符号,发现代码实际上严重依赖于 NIB 文件,即使您没有按代码方式接触它。 (我猜又是 MVC..)

是否有一种简单的方法来编译您编写的代码,无需添加 NIB 文件,只需编写您编写的代码和您添加的框架?我认为这将是一个空白项目,但我的经验告诉我不是这样?!

【问题讨论】:

  • 您的意思是“以编程方式”吗?
  • Programmatic 并不意味着对 MVC 不利。 MVC 只是如何组织组件代码,IB/NIB/Storyboard 与 MVC 无关。它们只是帮助您绘制 UI 的工具。你完全可以不用这些工具来做 MVC。

标签: xcode cocoa project


【解决方案1】:

这是我在应用程序中使用的方法。格式很抱歉,希望你能把它弄出来。我不知道如何在这里关闭自动格式化。

当然,这个例子中不会有任何可用的主菜单,这对我来说太多了,无法在这样的帖子上写:P - 对不起,做一些研究;)

这应该让你开始:

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

ma​​in.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;
}

【讨论】:

  • 我向那些不想在 Cocoa 应用程序中使用 NIB 的人推荐这个作为解决方案。一个典型的例子是帮助“代理”设置为登录项。
  • 太好了,谢谢!我意识到这是一个 3 年前的答案,但我想知道您是否可以详细说明为什么需要实例化 NSAutoreleasePool,尤其是返回“EXIT_SUCCESS”。
  • 对于 ARC 代码,使用 @autoreleasepool 块而不是实例化和排出/释放 NSAutoreleasePool,如下所述:developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
  • 我认为表达式应该是 [[[MyApplicationDelegate alloc] init] autorelease]
【解决方案2】:
int main() {
    [NSAutoreleasePool new];
    [NSApplication sharedApplication];
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
    id menubar = [[NSMenu new] autorelease];
    id appMenuItem = [[NSMenuItem new] autorelease];
    [menubar addItem:appMenuItem];
    [NSApp setMainMenu:menubar];
    id appMenu = [[NSMenu new] autorelease];
    id appName = [[NSProcessInfo processInfo] processName];
    id quitTitle = [@"Quit " stringByAppendingString:appName];
    id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:quitTitle
    action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
    [appMenu addItem:quitMenuItem];
    [appMenuItem setSubmenu:appMenu];
    id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 200)
    styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
    autorelease];
    [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
    [window setTitle:appName];
    [window makeKeyAndOrderFront:nil];
    [NSApp activateIgnoringOtherApps:YES];
    [NSApp run];
    return 0;
}

【讨论】:

【解决方案3】:

虽然这是几年前的问题......

这是在 Swift 中引导 Cocoa 应用程序的最小代码 sn-p。

import AppKit

final class ExampleApplicationController: NSObject, NSApplicationDelegate {
    let window1 =   NSWindow()

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        window1.setFrame(CGRect(x: 0, y: 0, width: 800, height: 500), display: true)
        window1.makeKeyAndOrderFront(self)
    }

    func applicationWillTerminate(aNotification: NSNotification) {
    }

}

autoreleasepool { () -> () in
    let app1        =   NSApplication.sharedApplication()
    let con1        =   ExampleApplicationController()

    app1.delegate   =   con1
    app1.run()
}

另外,我正在为 Cocoa 维护一堆编程示例,包括引导、窗口、菜单创建。

查看所需语言的子项目。

【讨论】:

  • 我在 autoreleasepool 行收到了一个错误Expressions are not allowed at the top level。 Xcode 6.4。
  • 我发现文件名必须是main.swift
【解决方案4】:

当然,您可以只编写代码而不使用 Interface Builder。

您检查过您的 Info.plist 吗?默认情况下,那里有一个 MainMenu.xib 条目,它可能是它抱怨的那个引用。

【讨论】:

  • 我没有接触过Interface Builder!这就是困扰我的问题,从 Info.plist 中删除条目已解决此问题 - 谢谢!是一个非常迅速的答复! :D
【解决方案5】:

带有NSToolbarNSMenu 的Swift 4 版本(使用事件处理程序而不是委托):

文件ma​​in.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()
}

文件: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)
         }
      }
   }

}

文件 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)
      }
   }
}

文件 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
   }
}

文件 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
   }
}

文件 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
   }

}

便利扩展

文件 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
   }

}

文件 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()
      }
   }
}

文件 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))
      }
   }
}

文件 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()
      }
   }
}

文件 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)
   }
}

【讨论】:

    【解决方案6】:

    问题可能是您仍在main 函数中调用NSApplicationMain(在main.m 中)。如果您没有加载诸如 MainMenu.nib 之类的 nib,则可能必须取消对 NSApplicationMain 的调用并在 main 中编写自己的代码以启动应用程序。

    【讨论】:

    • 我实际上希望这样做,但我认为 NSApplicationMain 必须只从 .plist 文件中获取 .nib 信息 - 因为它似乎运行良好!为快速回复干杯!
    • 记录在案 - 在我重新实现 NSApplication 之前,我还没有注意到一两个问题!所以你们俩都是对的,再次欢呼。 (以防有人想知道-“ [NSApplication shareApplication]”-接着-“ [NSApp run”-足以修复最终错误)
    【解决方案7】:

    这里是 Casper's solution,根据 Marco's suggestion 为 ARC 更新:

    #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;
    }
    

    【讨论】:

      【解决方案8】:

      晚了7年,不过单文件代码简单一点

      #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;
      }
      

      【讨论】:

        【解决方案9】:

        当然,现在回答这个问题为时已晚,但对于任何想在没有 Xib(Nib) 文件的情况下创建 iOS 应用程序的人来说,都应该记住这一点。

        注意:虽然您可以在不使用 nib 文件的情况下创建 Objective-C 应用程序,但这样做非常罕见且不推荐。根据您的应用程序,避免使用 nib 文件可能需要您替换大量框架行为才能获得与使用 nib 文件相同的结果。

        See this Documentation to know more what apple has to say on this approach

        我希望这可以帮助将来的人。谢谢!

        【讨论】:

          【解决方案10】:

          autoreleasepool sn-p provided above 的示例 swift 代码在现代 Xcode 中不起作用。相反,您需要删除 App Delegate 源文件中的 @NSApplicationMain(如果有)(Xcode 现在为新项目添加这些),并添加一个包含以下内容的 main.swift 文件:

          上面的顶级代码示例不再适用于最新版本的 Xcode。而是使用这个:

          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)
          

          【讨论】:

          • 技术上应该编辑旧帖子(社区 wiki),但您没有足够的声誉。所以我想一个新的答案是可以的。您可以通过明确链接到“上面提供的”答案来改进您的答案。
          【解决方案11】:

          不要使用 NSApplication 和 NSApp ...

          您只需指定实现 UIApplicationDelegate 协议的类:

          UIApplicationMain(argc, argv, nil, @"Name of your class");
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-10-20
            • 1970-01-01
            • 2013-01-28
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多