Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

programmatically create initial window of cocoa app (OS X)

Usually I am making iOS app but now I am trying to make an OS X app, and I am lost at the very beginning. Say the style I make the iOS apps are totally programmatic, there's no xib files or whatsoever just because that I have a lot more control by typing than dragging. However in OS X programming, it starts with some xib files with the menu items and a default window. There are quite a lot of items in the menu items so that's probably not something I want to mess around, but I want to programmatically create my first window myself.

So I did this:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {         NSUInteger windowStyleMask = NSTitledWindowMask|NSResizableWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask;     NSWindow* appWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(200, 200, 1280, 720) styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];     appWindow.backgroundColor = [NSColor lightGrayColor];     appWindow.minSize = NSMakeSize(1280, 720);     appWindow.title = @"Sample Window";     [appWindow makeKeyAndOrderFront:self];     _appWindowController = [[AppWindowController alloc] initWithWindow:appWindow];     [_appWindowController showWindow:self]; } 

So here, I have created a window first, and use that windowController to init this window. The window does show up in this way, but I can only specify the inner elements, like buttons and labels here, but not in the windowController. It makes me feel bad so I tried another way.

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {     _appWindowController = [[AppWindowController alloc] init];     [_appWindowController showWindow:self]; } 

and after this I want to set the other elements in the loadWindow: function in the windowController like this:

- (void)loadWindow {     [self.window setFrame:NSMakeRect(200, 200, 1280, 720) display:YES];     self.window.title = @"Sample window";     self.window.backgroundColor = [NSColor lightGrayColor];          NSButton* sampleButton = [[NSButton alloc] initWithFrame:NSRectFromCGRect(CGRectMake(100, 100, 200, 23))];     sampleButton.title = @"Sample Button!";     [sampleButton setButtonType:NSMomentaryLightButton];     [sampleButton setBezelStyle:NSRoundedBezelStyle];     [self.window.contentView addSubview:sampleButton];     NSLog(@"Loaded window!");     [self.window makeKeyAndOrderFront:nil]; } 

Unfortunately, this never works. the loadWindow: never gets called, nor windowDidLoad:. Where did they go?

And please don't ask why I don't use nibs. I wish to make some highly customized views inside, possibly OpenGL, so I don't think nibs can handle it. I am greatly appreciated if anyone could help. Thanks.

And also, who knows how to even start the menu items from scratch, programmatically?

I am using the latest Xcode.

like image 215
wlicpsc Avatar asked Mar 29 '13 00:03

wlicpsc


2 Answers

I spent an entire Sunday digging into this problem myself. Like the person asking the question, I prefer coding iOS and OSX without nib files (mostly) or Interface Builder and to go bare metal. I DO use NSConstraints though. It is probably NOT WORTH avoiding IB if you're doing simpler UIs, however when you get into a more complex UI it gets harder.

It turns out to be fairly simple to do, and for the benefit of the "Community" I thought I'd post a concise up to date answer here. There ARE some older Blog Posts out there and the one I found most useful were the ones from Lap Cat Software. 'Tip O The Hat' to you sir!

This Assumes ARC. Modify your main() to look something like this:

#import <Cocoa/Cocoa.h> #import "AppDelegate.h"  int main(int argc, const char *argv[]) {     NSArray *tl;     NSApplication *application = [NSApplication sharedApplication];     [[NSBundle mainBundle] loadNibNamed:@"MainMenu" owner:application topLevelObjects:&tl];      AppDelegate *applicationDelegate = [[AppDelegate alloc] init];      // Instantiate App  delegate     [application setDelegate:applicationDelegate];                      // Assign delegate to the NSApplication     [application run];                                                  // Call the Apps Run method      return 0;       // App Never gets here. } 

You'll note that there is still a Nib (xib) in there. This is for the main menu only. As it turns out even today (2014) apparently no way to easily set the position 0 menu item. That's the one with the title = to your App name. You can set everything to the right of it using [NSApplication setMainMenu] but not that one. So I opted to keep the MainMenu Nib created by Xcode in new projects, and strip it down to just the position 0 item. I think that is a fair compromise and something I can live with. One brief plug for UI Sanity... when you're creating Menus please follow the same basic pattern as other Mac OSX Apps.

Next modify the AppDelegate to look something like this:

-(id)init {     if(self = [super init]) {         NSRect contentSize = NSMakeRect(500.0, 500.0, 1000.0, 1000.0);         NSUInteger windowStyleMask = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;         window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];         window.backgroundColor = [NSColor whiteColor];         window.title = @"MyBareMetalApp";          // Setup Preference Menu Action/Target on MainMenu         NSMenu *mm = [NSApp mainMenu];         NSMenuItem *myBareMetalAppItem = [mm itemAtIndex:0];         NSMenu *subMenu = [myBareMetalAppItem submenu];         NSMenuItem *prefMenu = [subMenu itemWithTag:100];         prefMenu.target = self;         prefMenu.action = @selector(showPreferencesMenu:);          // Create a view         view = [[NSTabView alloc] initWithFrame:CGRectMake(0, 0, 700, 700)];     }     return self;  }   -(IBAction)showPreferencesMenu:(id)sender  {     [NSApp runModalForWindow:[[PreferencesWindow alloc] initWithAppFrame:window.frame]];  }  -(void)applicationWillFinishLaunching:(NSNotification *)notification {     [window setContentView:view];           // Hook the view up to the window }  -(void)applicationDidFinishLaunching:(NSNotification *)notification {     [window makeKeyAndOrderFront:self];     // Show the window }  

And Bingo... you're good to go! You can start working from there in the AppDelegate pretty much like you're familiar with. Hope that helps!

UPDATE: I don't create menus in code anymore as I've shown above. I've discovered you can edit MainMenu.xib source in Xcode 6.1. Works nice, very flexible and all it takes is a little experimentation to see how it works. Faster than messing around in code and easy to localize! See the picture to understand what I am on about:

Edit MainMenu.xib

like image 149
Cliff Ribaudo Avatar answered Sep 23 '22 02:09

Cliff Ribaudo


See https://github.com/sindresorhus/touch-bar-simulator/blob/master/Touch%20Bar%20Simulator/main.swift

In main.swift

let app = NSApplication.shared() let delegate = AppDelegate() app.delegate = delegate app.run() 
like image 22
onmyway133 Avatar answered Sep 26 '22 02:09

onmyway133