Creating a Cocoa application without NIB files
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. However, I have a problem...
开发者_如何学PythonI create an empty project, copy all the frameworks over and set the build settings - and I get errors about the executable, or lack of executable. The build settings all appear fine, but it tells me there is no executable - it will build + run fine. However it doesn't run. There is no error either - it just appears to run very fast and cleanly! Unless I try and run GDB which politely tells me I need to give it a file first..
Running…
No executable file specified.
Use the "file" or "exec-file" command.
So I created a Cocoa application, removed all the stuff I didn't need (that is, the MainMenu.xib
file..), and now I can compile my code perfectly. However it dies complaining that it's
"Unable to load nib file: MainMenu, exiting"
I have gone through the Project Symbols and see that the code actually relies upon the NIB file heavily, even if you don't touch it code-wise. (MVC again I guess..)
Is there a simple way to compile just what you code, no added NIB files, just the code you write and the frameworks you add? I assume it would be a blank project, but my experience tells me otherwise?!
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;
}
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;
}
Though this is a few years old question...
Here's minimal code snippet to bootstrap a Cocoa application in 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()
}
Also, I am maintaining a bunch of programmatic examples for Cocoa including bootstrapping, window, menu creations.
- CocoaProgrammaticHowtoCollection
See subprojects for desired language.
- Swift examples
- Objective-C Examples
Of course you can write just code and not use Interface Builder.
Have you checked your Info.plist? By default there is an entry there for MainMenu.xib and it may be that reference it's complaining about.
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)
}
}
The problem might be that you're still calling NSApplicationMain
in your main
function (in main.m
). If you're not loading a nib such as MainMenu.nib
, you'll probably have to rip out the call to NSApplicationMain
and write your own code in main
for starting the application.
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;
}
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;
}
Of course, it's too late to answer on this but for anyone who is thinking on creating iOS App without Xib(Nib) files should keep this thing in mind.
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.
See this Documentation to know more what apple has to say on this approach
I hope this could help someone in future. Thanks!
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)
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");
精彩评论