programmatically create initial window of cocoa app (OS X) - objective-c

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.

Related

Create Mojave Cocoa window programmatically in 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:.

Presenting modal dialogs from XIB in Cocoa: best/shortest pattern?

Below is my typical WindowController module for presenting a modal dialog (could be settings, asking username/password, etc) loaded from a XIB. It seems a bit too complex for something like this. Any ideas how this can be done better/with less code?
Never mind that it's asking for a password, it could be anything. What frustrates me most is that I repeat the same pattern in each and every of my XIB-based modal window modules. Which of course means I could define a custom window controller class, but before doing that I need to make sure this is really the best way of doing things.
#import "MyPasswordWindowController.h"
static MyPasswordWindowController* windowController;
#interface MyPasswordWindowController ()
#property (weak) IBOutlet NSSecureTextField *passwordField;
#end
#implementation MyPasswordWindowController
{
NSInteger _dialogCode;
}
- (id)init
{
return [super initWithWindowNibName:#"MyPassword"];
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self.window center];
}
- (void)windowWillClose:(NSNotification*)notification
{
[NSApp stopModalWithCode:_dialogCode];
_dialogCode = 0;
}
- (IBAction)okButtonAction:(NSButton *)sender
{
_dialogCode = 1;
[self.window close];
}
- (IBAction)cancelButtonAction:(NSButton *)sender
{
[self.window close];
}
+ (NSString*)run
{
if (!windowController)
windowController = [MyPasswordWindowController new];
[windowController loadWindow];
windowController.passwordField.stringValue = #"";
if ([NSApp runModalForWindow:windowController.window])
return windowController.passwordField.stringValue;
return nil;
}
The application calls [MyPasswordWindowController run], so from the point of view of the user of this module it looks simple, but not so much when you look inside.
Set tags on your buttons to distinguish them. Have them both target the same action method:
- (IBAction) buttonAction:(NSButton*)sender
{
[NSApp stopModalWithCode:[sender tag]];
[self.window close];
}
Get rid of your _dialogCode instance variable and -windowWillClose: method.
-[NSApplication runModalForWindow:] will already center the window, so you can get rid of your -awakeFromNib method.
Get rid of the invocation of -[NSWindowController loadWindow]. That's an override point. You're not supposed to call it. The documentation is clear on that point. It will be called automatically when you request the window controller's -window.
Get rid of the static instance of MyPasswordWindowController. Just allocate a new one each time. There's no point in keeping the old one around and it can be troublesome to reuse windows.

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).

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

UISplitViewController programmatically without nib/xib

I usually create my projects without IB-stuff. The first thing I do is to strip off all references to xibs, outlets updated plist, etc and so forth. No problems, works great (in my world)!
Now, I just installed 3.2 and tried to develop my first iPad app. Following same procedure as before, I created a UISplitView-based application project and stripped off all IB-stuff. Also, I followed the section in Apple's reference docs: Creating a Split View Controller Programmatically, but nevertheless, the Master-view is never shown, only the Detail-view is (no matter what the orientation is). I really have tried to carefully look this through but I cannot understand what I have missed.
Is there a working example of a UISplitViewController without the nibs floating around somewhere? I have googled but could not find any. Or do you know what I probably have missed?
Declare your splitviewcontroller in your delegate header, use something like this in your didfinishlaunching
ensure you add the UISplitViewControllerDelegate to the detailedViewController header file and that you have the delegate methods aswell. remember to import relevant header files
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
splitViewController = [[UISplitViewController alloc] init];
rootViewController *root = [[rootViewController alloc] init];
detailedViewController *detail = [[detailedViewController alloc] init];
UINavigationController *rootNav = [[UINavigationController alloc] initWithRootViewController:root];
UINavigationController *detailNav = [[UINavigationController alloc] initWithRootViewController:detail];
splitViewController.viewControllers = [NSArray arrayWithObjects:rootNav, detailNav, nil];
splitViewController.delegate = detail;
[window addSubview:splitViewController.view];
EDIT - as per Scott's excellent suggestion below, don't add to the windows subview, instead
[self.window setRootViewController:(UIViewController*)splitViewController]; // that's the ticket
[window makeKeyAndVisible];
return YES;
}
//detailedView delegate methods
- (void)splitViewController:(UISplitViewController*)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem*)barButtonItem
forPopoverController:(UIPopoverController*)pc
{
[barButtonItem setTitle:#"your title"];
self.navigationItem.leftBarButtonItem = barButtonItem;
}
- (void)splitViewController:(UISplitViewController*)svc
willShowViewController:(UIViewController *)aViewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
self.navigationItem.leftBarButtonItem = nil;
}
I also prefer code to IB ;-)
Oldish thread, but thought I'd spare reader time + grief when the above technique fails to produce a UISplitViewController that responds correctly to device orientation change events. You'll need to:
Ensure all subordinate views respond properly in
shouldAutorotateToInterfaceOrientation. Nothing new here.
Rather than add the UISplitViewController's view to the main window,
[window addSubview:splitViewController.view]; // don't do this
instead set the main window's root controller to the UISplitViewController:
[self.window setRootViewController:(UIViewController*)splitViewController]; // that's the ticket
Adding the splitviewcontroller's view as a subview of the main window (barely) allows it to co-present with sibling views, but it doesn't fly with UISplitViewController's intended use case. A UISplitViewController is a highlander view; there can only be one.
Swift 5.2
iOS 13
Both master and detail view controllers are embedded in navigation controllers
let splitViewController = UISplitViewController()
splitViewController.delegate = self
let masterVC = MasterViewController()
let detailVC = DetailViewController()
let masterNavController = UINavigationController(rootViewController: masterVC)
let detailNavController = UINavigationController(rootViewController: detailVC)
splitViewController.viewControllers = [masterNavController,detailNavController]
You can put this code in your AppDelegate's (or in SceneDelegate if your target is iOS 13.0+)didFinishLaunchingWithOptions function. Just remember to make the splitViewController your rootViewController like this
self.window!.rootViewController = splitViewController
I had just met the same problem.
make sure that your child viewController of splitview can Autorotate to interface orientation.
you can change the function in your childViewController like this:
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
return YES;
}
then the master view will be shown.