Create Mojave Cocoa window programmatically in Objective-C - objective-c

I'm trying to create a minimal application in order for me to start a game engine from scratch. Here is the code:
#import <Cocoa/Cocoa.h>
int main (int argc, const char * argv[]){
NSWindow *window = [[NSWindow alloc] init];
[window makeKeyAndOrderFront:nil];
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false)
while (1);
return 0;
}
I would how to display a window without calling CFRunLoopRunInMode().
Xcode 10.1
MacOS 10.14.3

I think I have found the answer myself. The window does not appear unless it receives the nextEventMatchingMask: message. This is probably what triggers the window in a CFRunLoop and is what I wanted to know, although it would be nice if I could dig deeper. For now, I'm happy with the following solution.
#import <Cocoa/Cocoa.h>
int main (int argc, const char * argv[]){
#autoreleasepool {
// Create a default window
NSWindow *window = [[NSWindow alloc] init];
// Make it blue just for better visibility
[window setBackgroundColor:[NSColor blueColor]];
// Bring to front and make it key
[window makeKeyAndOrderFront:nil];
// Custom run loop
NSEvent* event;
while(1) {
do {
event = [window nextEventMatchingMask:NSEventMaskAny]; //window shows now
if ([event type] == NSEventTypeLeftMouseDown) {
NSLog(#"Mouse down");
}
else {
NSLog(#"Something happened");
}
} while (event != nil);
}
}
return 0;
}
I don't have a reference for that. I can only refer to this article: Handmade Hero for mac in which the window appears due to a similar method. That was not good enough for me because such a method involves NSApp, which I would like to avoid, if possible.

What you have is not a Cocoa app.
You need to use the Xcode template to create a simple, one window, Cocoa app. That template will include a main() that starts the AppKit (NSApplicationMain(argc,argv);). This function performs the (approximately) 5,000 little things that make a Cocoa app run.
In the app bundle's Info.plist you either define a custom subclass of NSApplication to run your app or, much more commonly, in MainMenu.xib you define a custom NSApplicationDelegate object.
Once the AppKit has initialized and is ready to start your application, both of those objects will receive a message that you can override and add your custom startup code.
The standard template does all of that, so just use it to create a new project and then add your code to -applicationDidFinishLaunching:.

Related

Cannot set view for NSOpenGLContext

(Sorry in advance for the seemingly large amount of code here) I'm trying to create a window with an OpenGL context with Cocoa, but I'm finding that I am unable to set the view property of the NSOpenGLContext that I create.
I cannot simply use an NSOpenGLView, since I need to interface with a C++ drawing backend and use multiple contexts. The code I post here is just me trying to get to grips with handling NSOpenGLContexts, but it will be used in a much larger project. This is why I'm instantiating NSApplication and my NSWindow manually rather than through a NIB/NSApplicationMain.
My main.m file:
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#import "Delegate.h"
int main(int argc, const char * argv[]) {
[NSApplication sharedApplication];
Delegate* dlg = [[Delegate alloc] init];
[NSApp setDelegate:dlg];
[NSApp run];
return 0;
}
I then have my delegate class, and I'll refrain from posting the file Delegate.h since it'll be pretty obvious what's there given these contents of Delegate.m:
#import <Cocoa/Cocoa.h>
#import "Delegate.h"
#import <OpenGL/gl.h>
#implementation Delegate
- (void) draw
{
[self.glContext makeCurrentContext];
glClearColor(1, 0, 1, 1);
glClear(GL_COLOR_BUFFER_BIT);
[self.glContext flushBuffer];
}
- (void) applicationDidFinishLaunching:(NSNotification *)notification
{
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
self.win = [[NSWindow alloc] initWithContentRect:NSMakeRect(30, 30, 300, 200)
styleMask:NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask
backing:NSBackingStoreBuffered
defer:YES];
NSOpenGLPixelFormatAttribute glAttributes[] =
{
NSOpenGLPFAColorSize, 24,
NSOpenGLPFAAlphaSize, 8,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFAAccelerated,
0
};
self.glContext = [[NSOpenGLContext alloc] initWithFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:glAttributes]
shareContext:nil];
[self.glContext setView: [self.win contentView]];
printf("view:%p, contentView:%p\n", [self.glContext view], [self.win contentView]);
[self.win makeKeyAndOrderFront:nil];
[NSTimer
scheduledTimerWithTimeInterval:.1
target:self
selector:#selector(draw)
userInfo:nil
repeats:YES];
}
The window opens just fine. I am able to tell that -applicationDidFinishLaunching and -draw are being called. The window shows up empty however.
The printf call shows that the view property of self.glContext is equal to the address 0x0. I see no documentation or other forum threads about why I would be unable to set the drawable object of an NSOpenGLContext.
I have tried putting the NSOpenGLContext in its own subclass of NSView and adding that subclass as a subview of the window's content view, but with no success.
Try setting the defer parameter of -[NSWindow initWithContentRect:...] to NO. You may also wish to set the GL context's view after ordering the window on-screen.
Basically, -[NSOpenGLContext setView:] can fail if the view's window doesn't yet have a "device". When that's happened to me, it usually logs a message to the console about an "invalid drawable", but I haven't checked in recent versions of the OS.
Also, you need to register as an observer of the NSViewGlobalFrameDidChangeNotification notification from the view and, in response, call -update on the GL context object.

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.
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:
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()
Swift 4:
// 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
public class Application: NSApplication {
private lazy var mainWindowController = MainWindowController()
private lazy var mainAppMenu = MainMenu()
override init() {
super.init()
delegate = self
mainMenu = mainAppMenu
}
public required init?(coder: NSCoder) {
super.init(coder: coder) // This will newer called.
}
}
extension Application: NSApplicationDelegate {
public func applicationDidFinishLaunching(_ aNotification: Notification) {
mainWindowController.showWindow(nil)
}
}
Override the -init method in your AppWindowController class to create the window and then call super's -initWithWindow: method (which is NSWindowController's designated initializer) with that window.
But I generally agree with the comments that there's little reason to avoid NIBs.

Best design pattern for a window opening another window in cocoa application

I am learning how to create osx applications with Cocoa/Objective-C. I am writing a simple app which will link together two different tutorials I have been going through. On start up a choice window loads with 2 buttons, one button loads one window and the other loads the other window. When either button is clicked the choice window closes.
The choice window controller object was added to the MainMenu.xib file so it is created at launch. The window is then opened using the awakeFromNib message.
I want the result of one button to open up the 'track controller' tutorial application from the ADC website. The action looks like this:
- (IBAction)trackButton:(id)sender {
TMTrackController *trackController = [[TMTrackController alloc] init];
[self.window close];
}
I added an init method to the TMTrackController class which looks like this:
- (id) init {
if (self = [super init]) {
[self showWindow];
TMTrack *myTrack = [[TMTrack alloc] init];
myTrack.volume = 50;
self.track = myTrack;
[self updateUserInterface];
return self;
}
else {
return nil;
}
}
- (void) showWindow {
if(!self.window) {
[NSBundle loadNibNamed:#"trackWindow" owner:self];
}
[self.window makeKeyAndOrderFront:self];
}
I am not sure this is the best way to be doing this as I know that the choiceController class will be released when it is closed thus getting rid of the TMTrackController class too. However even when I untick the 'release when closed' box of the ChoiceWindow.xib it breaks too.
What is the correct way to do this?
With xib s in the same project use:
#interface
#property (strong) NSWindowController *test;
#implementation
#synthesize test;
test = [[NSWindowController alloc] initWithWindowNibName:#"XIB NAME HERE"];
[test showWindow:self];
[home close];
It is not completely the same but this is my solution for such problems: Stackoverflow
Just ignore my statement in this answer regarding showing the window as a modal window. Everything else is still valid. This way you could have your personal window controller and it controls everything there is within the xib. This is a huge advantage for maintaining the project afterwards (and you keep to the application logic).

Objective-C: window undeclared

It will be a noob question, but i'm getting crazy with this. I've read a tons of topics but i think i am missing something main.
I`ve created new cocoa app project, did not change anything, just add next code to main.m
int main(int argc, char *argv[])
{
NSView *superview = [window contentView];
NSRect frame = NSMakeRect(10, 10, 200, 100);
NSButton *button = [[NSButton alloc] initWithFrame:frame];
[button setTitle:#"Click me!"];
[superview addSubview:button];
[button release];
return NSApplicationMain(argc, (const char **) argv);
}
During compiling xcode tells me that window undeclared. I understand that instead of window should be name of my NSWindow object, but i don`t understand which name has NSWindow which automatically created in MainMenu.xib file.
Please help, i`m almost ready to broke the wall with my head.
At that point no window has even been created yet. The code generated by Xcode gives you window in your projects application delegate, so add your code to -applicationDidFinishLaunching: in YourProjectAppDelegate.m instead.
I recommend to start with some introductory material like Hillegass where such things should be covered in detail.

problems with creating an NSWindow

I am new to Cocoa, and I am just experimenting with creating a window programmatically (without using Interface Builder).
I start a new Cocoa Application in Xcode, then I remove the window from the nib file in Interface Builder to replace it with my own one.
In the main function, I add the code:
NSWindow* myWindow;
myWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(10,100,400,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
When I try to build and run the application, I receive the following error message:
Error (1002) creating CGSWindow
Why does this happen??? What is a CGSWindow by the way?
Rainer
You probably don't have a connection to the window server yet. That's NSApplication's job, so try creating the shared application first.
If that doesn't help, I'd just go through with my usual application layout: Create an NSObject subclass for a custom controller, instantiate this from your application delegate's applicationWillFinishLaunching: and release it in applicationWillTerminate:, and have your custom controller's init method create the window. The application object will definitely be running by this point (as main does nothing but call NSApplicationMain, which gets/creates the shared application and tells it to run), so you should definitely have your connection to the window server and so be able to create the window.
Move the code you've written into the following method from your App Delegate implementation file -
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSWindow* myWindow;
myWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(10,100,400,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];
}
and it should load your window perfectly.
It's worth noting that it's never safe and is not a good practice to create any UI related objects in your main function.
If you want to display a completely empty window from scratch this is all the code you'll need:
//if you used a template this will be already in the file:
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoReleasePool alloc] init];
int retval=UIApplicationMain(argc,argv,nil,#"SimpleWindowAppDelegate");
[pool release];
return retVal;
}
#implementation SimpleWindowAppDelegate : NSObject <UIApplicationDelegate>
-(void) applicationDidFinishLaunching:(UIApplication *)application
{
UIWindow *window=[[UIWindow alloc] initWithFrame:[[UIDevice mainScreen] bounds]];
//You could create views and add them here:
//UIView *myView=[[UIView alloc] initWithFrane:CGRectMake(0,0,50,50)];
//[window addSubView:myView];
//[myView release];
[window makeKeyAndVisible];
}
#end
The only code that you will probably ever want to have in your main() function in a Cocoa application is automatically created for you by XCode (if you are using it).
I suggest that you add the code that you want into your applicationDelegate's -applicationDidFinishLaunching: method
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification {
myWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(10,100,400,300)
styleMask:NSTitledWindowMask
backing:NSBackingStoreBuffered
defer:NO];;
}