Создание приложения Какао без файлов NIB

17

Да, я знаю, что это противоречит всему принципу MVC!

Однако я просто пытаюсь создать довольно тривиальное приложение - и я в значительной степени реализовал его. Однако у меня есть проблема ...

Я создаю пустой проект, копирую все фреймворки и устанавливаю параметры сборки - и я получаю ошибки об исполняемом файле или об отсутствии исполняемого файла. Все параметры сборки выглядят нормально, но это говорит о том, что исполняемого файла нет - он будет работать + работает нормально. Однако это не работает. Там также нет ошибки - он просто работает очень быстро и чисто! Если я не попытаюсь запустить GDB, который вежливо говорит мне, что мне нужно сначала дать ему файл ..

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

Итак, я создал приложение Cocoa, удалил все ненужное (то есть файл MainMenu.xib ..), и теперь я могу отлично скомпилировать свой код. Однако он умирает, жалуясь, что это

"Unable to load nib file: MainMenu, exiting"

Я просмотрел символы проекта и вижу, что код на самом деле сильно зависит от файла NIB, даже если вы не касаетесь его в коде. (Снова MVC, я думаю ..)

Есть ли простой способ скомпилировать только то, что вы кодируете , нет добавленных файлов NIB, только код, который вы пишете, и фреймворки, которые вы добавляете? Я предполагаю, что это будет пустой проект, но мой опыт говорит мне иначе?!

    
задан Moddy 08.06.2010 в 14:44
источник
  • Вы имели в виду "программно?" –  Jun 8 '10 at 16:23 08.06.2010 в 18:23
  • Programmatic не означает что-то против MVC. MVC - это как организовать код компонента, а IB / NIB / Storyboard ничего не связано с MVC. Это просто инструменты, которые помогут вам нарисовать интерфейс. Вы можете делать MVC без этих инструментов. –  Eonil 12.10.2014 в 07:54

11 ответов

10

Конечно, вы можете писать только код и не использовать Interface Builder.

Вы проверили свой Info.plist? По умолчанию там есть запись для MainMenu.xib, и это может быть та ссылка, на которую он жалуется.

    
ответ дан slf 08.06.2010 в 14:48
  • Я не дотронулся до Interface Builder! Вот что меня избило, удалив запись из Info.plist, решила это - спасибо! Был очень быстрый ответ! : D –  Moddy 08.06.2010 в 14:55
34

Это метод, который я использую в своих приложениях. Извините за форматирование, надеюсь, вы сможете разобрать. Я не знаю, как отключить автоформатирование здесь.

Конечно, в этом примере не будет функционирующего главного меню, это слишком много кода, чтобы я мог написать его на посте, подобном следующему: 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

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;
}
    
ответ дан Casper B. Hansen 17.07.2010 в 19:04
  • Я рекомендую это как решение для тех, кто НЕ хочет использовать NIB в приложении Cocoa. Типичным примером этого является установка «агента» помощника в качестве элемента входа. –  Arvin 15.08.2012 в 15:17
  • Отлично, спасибо! Я понимаю, что это ответ на 3 года, но мне было интересно, можете ли вы уточнить, почему требуется создание NSAutoreleasePool и, особенно, возврат «EXIT_SUCCESS». –  Marco Lazzeri 13.06.2013 в 05:09
  • Для кода ARC используйте блок @autoreleasepool вместо создания экземпляра и удаления / выпуска NSAutoreleasePool, как описано здесь: developer.apple.com/library/mac/#documentation/Cocoa/Reference/... –  Marco Lazzeri 13.06.2013 в 05:19
  • Я считаю, что выражение должно быть [[[MyApplicationDelegate alloc] init] autorelease] –  M Katz 04.11.2016 в 17:47
20
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;
}
    
ответ дан Bosko Popovic 13.06.2012 в 10:06
  • Отличное объяснение этого минималистского приложения находится на cocoawithlove.com/2010/09/minimalist-cocoa-programming.html –  Buzzy 06.12.2012 в 01:21
  • удивительный! Спасибо огромное!!! Должно быть, один. –  07.11.2013 в 00:21
  • Как отметил @Buzzy, этот ответ был фактически украден из cocoawithlove.com/2010/09/minimalist-cocoa-programming.html –  robobrobro 28.07.2017 в 02:53
  • Объясните, как использование Interface Builder более удобно, чем это. –  mondaugen 21.09.2017 в 17:11
10

Хотя это вопрос нескольких лет ...

Вот минимальный фрагмент кода для начальной загрузки приложения Какао в Swift.

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

Кроме того, я поддерживаю кучу программных примеров для Какао, включая начальную загрузку, окно, создание меню.

Смотрите подпроекты для нужного языка.

ответ дан Eonil 12.10.2014 в 08:19
  • Я получил ошибку Выражения не разрешены на верхнем уровне на линии autoreleasepool. Xcode 6.4. –  hiroshi 29.08.2015 в 14:24
  • Я обнаружил, что имя файла ДОЛЖНО быть main.swift. –  hiroshi 29.08.2015 в 14:39
7

Возможно, проблема в том, что вы все еще вызываете NSApplicationMain в своей функции mainmain.m ). Если вы не загружаете перо, такое как MainMenu.nib , вам, вероятно, придется разорвать вызов NSApplicationMain и написать собственный код в main для запуска приложения.

    
ответ дан mipadi 08.06.2010 в 14:47
  • Я ожидал сделать это на самом деле, но я думаю, что NSApplicationMain должен просто взять информацию .nib из файла .plist - как кажется, работает нормально! Приветствия за быстрый ответ, хотя! –  Moddy 08.06.2010 в 14:59
  • Для записи - все еще остались одна или две проблемы, которые я не заметил до тех пор, пока не выполнил NSApplication! Таким образом, вы были правы, снова приветствовали. (Любое, кому было интересно - «[NSApplication shareApplication]», а затем - «[NSApp run» - было достаточно, чтобы исправить окончательные ошибки) –  Moddy 08.06.2010 в 15:25
4

Версия Swift 4 с NSToolbar и NSMenu (с обработчиками событий вместо делегатов):

Файл 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()
}

Файл: 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)
   }
}
    
ответ дан Vlad 15.10.2017 в 22:51
3

Здесь находится решение Каспера , обновленное для 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;
}
    
ответ дан jasongrlicky 08.03.2016 в 19:23
3

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;
}
    
ответ дан Leo 26.06.2017 в 11:34
0

Пример кода swift для фрагмента autoreleasepool , приведенного выше , не работает в современном Xcode. Вместо этого вам нужно избавиться от @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)
    
ответ дан Ted Lemon 11.12.2016 в 20:35
  • Технически старый пост должен быть отредактирован (вики сообщества), но у вас недостаточно репутации. Поэтому я думаю, что новый ответ в порядке. Вы можете улучшить свой ответ, указав явную ссылку на ответ «приведенный выше». –  MikeJRamsey56 11.12.2016 в 21:06
0

Конечно, уже слишком поздно отвечать на этот вопрос, но всем, кто задумывается о создании iOS-приложения без файлов Xib (Nib), следует помнить об этом.

Note: Although you can create an Objective-C application without using nib files, doing so is very rare and not recommended. Depending on your application, avoiding nib files might require you to replace large amounts of framework behavior to achieve the same results you would get using a nib file.

См. эту документацию, чтобы узнать, что Apple может сказать по этому поводу. подход

Я надеюсь, что это может помочь кому-то в будущем. Спасибо!

    
ответ дан iHarshil 13.03.2018 в 10:21
-3

Не используйте NSApplication и NSApp ...

Вам просто нужно указать класс, который реализует протокол UIApplicationDelegate:

UIApplicationMain(argc, argv, nil, @"Name of your class");
    
ответ дан tata 08.06.2010 в 16:06
  • Это код iOS. Он специально спрашивал Mac-код. –  CajunLuke 15.07.2011 в 16:32